为什么讲策略模式
策略模式,应该是工作中比较常用的设计模式,调用方自己选择用哪一种策略完成对数据的操作,也就是“一个类的行为或其算法可以在运行时更改”我个人的理解是 将一些除了过程不同其他都一样的函数封装成策略,然后调用方自己去选择想让数据执行什么过程策略。常见的例子为根据用户分类推荐不同的排行榜(用户关注点不一样,推荐榜单就不一样)和单例模式一样,随着时间发展,我不再推荐经典策略模式,更推荐简单策略用枚举策略模式,复杂地用工厂策略模式。下面引入一个例子,我们的需求是:对一份股票数据列表,给出低价榜、高价榜、涨幅榜。这其中只有排序条件的区别,比较适合作为策略模式的例子
经典策略模式
数据DTO
@Data public class Stock { private String code; private Double price; private Double rise; }
|
抽象得到的策略接口
public interface Strategy {
List<Stock> sort(List<Stock> source); }
|
实现我们的策略类
public class HighPriceRank implements Strategy { @Override public List<Stock> sort(List<Stock> source) { return source.stream() .sorted(Comparator.comparing(Stock::getPrice).reversed()) .collect(Collectors.toList()); } }
public class LowPriceRank implements Strategy { @Override public List<Stock> sort(List<Stock> source) { return source.stream() .sorted(Comparator.comparing(Stock::getPrice)) .collect(Collectors.toList()); } }
public class HighRiseRank implements Strategy { @Override public List<Stock> sort(List<Stock> source) { return source.stream() .sorted(Comparator.comparing(Stock::getRise).reversed()) .collect(Collectors.toList()); } }
|
经典的Context类
public class Context { private Strategy strategy; public void setStrategy(Strategy strategy) { this.strategy = strategy; } public List<Stock> getRank(List<Stock> source) { return strategy.sort(source); } }
|
于是 我们顺礼成章地得到调用类—榜单实例RankServiceImpl
@Service public class RankServiceImpl {
@Resource private DataService dataService;
public List<Stock> getRank(String rankType) { Context context = new Context(); switch (rankType) { case "HighPrice": context.setStrategy(new HighPriceRank()); break; case "LowPrice": context.setStrategy(new LowPriceRank()); break; case "HighRise": context.setStrategy(new HighRiseRank()); break; default: throw new IllegalArgumentException("rankType not found"); } return context.getRank(dataService.getSource()); } }
|
我们可以看到经典方法,创建了一个接口、三个策略类,还是比较啰嗦的。调用类的实现也待商榷,新增一个策略类还要修改榜单实例(可以用抽象工厂解决,但是复杂度又上升了)。加之我们有更好的选择,所以此处不再推荐经典策略模式
基于枚举的策略模式
这里对这种简单的策略,推荐用枚举进行优化。
枚举的本质是创建了一些静态类的集合。
我下面直接给出例子,大家可以直观感受一下
枚举策略类
public enum RankEnum { HighPrice { @Override public List<Stock> sort(List<Stock> source) { return source.stream() .sorted(Comparator.comparing(Stock::getPrice).reversed()) .collect(Collectors.toList()); } }, LowPrice { @Override public List<Stock> sort(List<Stock> source) { return source.stream() .sorted(Comparator.comparing(Stock::getPrice)) .collect(Collectors.toList()); } }, HighRise { @Override public List<Stock> sort(List<Stock> source) { return source.stream() .sorted(Comparator.comparing(Stock::getRise).reversed()) .collect(Collectors.toList()); } }; public abstract List<Stock> sort(List<Stock> source); }
|
对应的调用类也得以优化,榜单实例RankServiceImpl
@Service public class RankServiceImpl {
@Resource private DataService dataService;
public List<Stock> getRank(String rankType) { RankEnum rank = RankEnum.valueOf(rankType); return rank.sort(dataService.getSource()); } }
|
可以看到,如果策略简单的话,基于枚举的策略模式优雅许多,调用方也做到了0修改,但正确地使用枚举策略模式需要额外考虑以下几点
基于工厂的策略模式
为了解决良好的扩展性和可维护性,我更推荐以下利用spring自带beanFactory的优势,实现一个基于工厂的策略模式。
策略类改动只是添加了@Service注解,并指定了Service的value属性
@Service("HighPrice") public class HighPriceRank implements Strategy { @Override public List<Stock> sort(List<Stock> source) { return source.stream() .sorted(Comparator.comparing(Stock::getPrice).reversed()) .collect(Collectors.toList()); } }
@Service("LowPrice") public class LowPriceRank implements Strategy { @Override public List<Stock> sort(List<Stock> source) { return source.stream() .sorted(Comparator.comparing(Stock::getPrice)) .collect(Collectors.toList()); } }
@Service("HighRise") public class HighRiseRank implements Strategy { @Override public List<Stock> sort(List<Stock> source) { return source.stream() .sorted(Comparator.comparing(Stock::getRise).reversed()) .collect(Collectors.toList()); } }
|
调用类修改较大,接入借助spring工厂特性,完成策略类
@Service public class RankServiceImpl {
@Resource private DataService dataService;
@Resource private Map<String, Strategy> rankMap;
public List<Stock> getRank(String rankType) { if (!rankMap.containsKey(rankType)) { throw new IllegalArgumentException("rankType not found"); } Strategy rank = rankMap.get(rankType); return rank.sort(dataService.getSource()); } }
|
若使用的不是Spring,也可以找找对应框架的工厂模式实现,或者自己实现一个抽象工厂。
工厂策略模式会比枚举策略模式啰嗦,但也更加灵活、易扩展性和易维护。故简单策略推荐枚举策略模式,复杂策略才推荐工厂策略模式。