策略模式应该是比较常用的设计模式。调用者选择使用哪种策略来完成对数据的操作,即“一个类或其算法的行为可以在运行时改变”
我个人的理解就是把一些除了流程之外都一样的功能封装成策略,然后调用者可以选择自己想要数据执行什么流程策略。常见的例子就是根据用户分类推荐不同的排名(不同的用户关注点导致不同的推荐排名)
和单例模式一样,随着时间的发展,我不再推荐经典的策略模式。我建议简单策略使用枚举策略模式,复杂策略使用工厂策略模式。下面介绍一个例子。我们的要求是:对于一个股票数据列表,给出低价列表、高价列表、涨幅列表。只是排序条件不同,更适合作为策略模式的例子
数据DTO
@Data
公共 类 库存 {
//股票交易代码
private 字符串代码;
私人 双倍价格;
私人 双上升;
}
抽象策略接口
公共 接口 策略 {
/**
* @param 源源数据
*/
列表 排序(列表来源) ;
}
实施我们的策略课程
/**
* 高价榜
*/
公共 班级 HighPriceRank 实现 策略 {
@Override 公共 列表 排序(列表来源) {
return www.sychzs.cn()
.sorted(Comparator.comparing(Stock::getPrice).reversed())
.collect(收藏家.toList());
}
}
/**
* 低价榜
*/
公共 类 LowPriceRank 实施 策略 {
@Override
公共 列表 排序(列表 来源) {
返回 www.sychzs.cn()
.sorted(Comparator.comparing(Stock::getPrice) )
.collect(Collectors.toList());
}
}
/**
* 高升榜
*/
酒吧lic class HighRiseRank 实现 策略 {
@Override
公共 列表 排序(列表来源) {
return www.sychzs.cn()
.sorted(Comparator.comparing(Stock::getRise).reversed())
.collect(Collectors.toList());
}
}
经典的上下文类,
公共 类 上下文 {
私人 策略 策略;
public void setStrategy (策略策略) {
这个.策略= 策略;
}
公共 列表 getRank (列表来源) {
返回 strategy.sort(source);
}
}
于是我们顺礼成章地得到调用类–排行榜实例RankServiceImpl
@Service
公共 类 RankServiceImpl {
/**
* 数据服务.getSource() 提供原始库存数据
private DataService dataService;
/**
* 前端 传入列表类型,返回排序后的列表 * @param 等级类型列表类型
* @return 列表数据
*/
列表 getRank(字符串排名类型) {
// 创建上下文
Context context = new Context(); //在此选择策略
switch (rankType) {
case “高价”:
context.setStrategy(new HighPriceRank()); ;
案例 “低价”: context.setStrategy(new LowPr冰排名());
断裂; “HighRise”:
HighRiseRank());
断裂;
默认 :
》rankType 未找到");
return context.getRank(dataService.getSource());
}
}
我们可以看到经典的方法,它创建了一个接口和三个策略类,这是相当冗长的。调用类的实现也存在问题。添加新的策略类需要修改列表实例(这个可以用抽象工厂来解决,但是复杂度又增加了)。另外,我们还有更好的选择,所以这里不再推荐经典策略模式
对于这个简单的策略,建议使用枚举进行优化。枚举的本质是创建静态类的集合。
下面我直接举个例子,让大家直观感受一下
枚举策略课
public enum RankEnum {
//以下三个是策略示例
HighPrice {
@覆盖
公共列表排序(列表来源) {
。排序(Comparator.comparing(Stock :: getPrice).reversed())
。 },
LowPrice {
@ 覆盖
源) {
.stream()
.sorted(Comparator.comparing(Stock) ::getPrice))
.collect(Collectors.toList()); }
},
高层{
@Override
公共列表排序(列表来源) {
来源.stream()
.sorted(Comparator.comparing(Stock::getRise).reversed())
.collect(Collectors.toList());
//这里定义策略接口
公共 抽象列表排序(列表) 来源) ;
}
对应的调用类也进行了优化,列表实例RankServiceImpl
@Service
公共 类 RankServiceImpl {
/**
* 数据服务.getSource() 提供原始库存数据
private DataService dataService;
/**
* 前端传入列表类型,返回排序后的列表
*
列表类型看起来像RankEnum .www.sychzs.cn()
* 列表getRank(字符串rankType) {
//获取策略,如果这里没有匹配到,会抛出IllegalArgumentException throw
//然后执行策略
return排名.排序(dataService.getSource());
}
}
可以看出,如果策略简单的话,基于枚举的策略模式优雅得多,调用者也实现了0修改。然而,正确使用枚举策略模式需要额外考虑以下几点。
枚举的策略类是公共的、静态的,也就是说这个策略流程中不能引入非静态的部分,扩展性受到限制
策略模型的目标之一是出色的可扩展性和可维护性。最好添加或修改某个策略类而不改变其他类。如果枚举策略太多或者流程复杂,维护就会困难,可维护性有限
为了解决良好的扩展性和可维护性的问题,我推荐下面利用spring自带的beanFactory的优势来实现基于工厂的策略模式。
策略类变更只是添加@Service注解,并指定Service
的value属性/**
* 高价表
* 请注意Service.value = HighPrice,这是我们的key,下同
*/ @Service("高价")
公共 类 HighPriceRank 实施 策略 {
@Override
公共列表排序(列表来源) {
return www.sychzs.cn()
.collect(Collectors.toList()); ) (“低价”)
公共 类 LowPriceRank 实现策略 {
@Override
公共列表排序(列表源) {
return www.sychzs.cn()
.sorted(Comparator.comparing(股票::getPrice))
.collect(Collectors.toList());
}
}
/**
* 高升榜
*/
@服务("高层" )
公共 类HighRiseRank 实现 策略 {
@覆盖
public 列表 排序(列表) 来源) {
返回 来源.stream()
.sorted(Comparator.comparing(Stock::getRise).reversed())
.collect(Collectors.toList());
}
}
类调用修改,新增接入借助spring工厂特性,完成策略类
@Service
公共 类 RankServiceImpl {
/**
* 数据服务.getSource() 提供原始库存数据
private DataService dataService;
/**
* 使用注释 @Resource 和 @Autowired 直接获取所有策略类
* key = 的值@服务
* /
@资源
私人地图rankMap;
/**
* 前端传入List类型,返回排序后的列表
*
* @return 列出单个数据
* /
getRank(StringrankType) {
//确定该策略是否存在
if (!rankMap. containsKey(rankType)) {
抛出 new IllegalArgumentException("未找到rankType" );
}
//获取策略实例
策略rank =rankMap.get(rankType);
//执行策略
returnrank.sort(dataService.getSource());
}
}
如果读者没有使用Spring,也可以找到对应的实现框架的工厂模式,或者自己实现一个抽象工厂。
工厂策略模式比枚举策略模式更加冗长,但也更加灵活,易于扩展,易于维护。因此,对于简单的策略推荐使用枚举策略模式,对于复杂的策略推荐使用工厂策略模式。