coldsmog开发笔记
JS 事件笔记
Ckeditor 上传WPS图片失败问题
Springboot
SpringBoot 统一异常处理
Springboot 引入外部jar包
Springboot 打成war包
Springboot 多环境配置
SpringBoot @Scope注解学习
Springboot 快速生成项目API文档
SpringCache 缓存
Spring jetcache 二级缓存
Springboot 按条件装配类
FastJson的JsonPath语法
正则表达式语法
Spring 路径匹配
Feign 基础数据操作
监控Feign调用metrics
Springboot feign的性能优化
Jackson 设置序列化属性
SpringBoot 集成 Spring Data Mongodb 操作 MongoDB
MongoDB 的一些注意事项
MongoDB 指令对比
Jackson 解析XML
Springboot Redis注册
SpringBoot RedisTemplate批量插入
Springboot 指标监控Micrometer
springboot validation 注解校验
springboot 引入配置
Springboot 静态文件处理
Springboot 导出csv文件
Springboot 事件驱动(发布/订阅模式)
Springboot 启动过程和扩展点
Springboot 优化停机上下线
Spring自动装配 - 干饭角度学习
Springboot ShardingJDBC
Springboot的重试
springboot 动态修改端口
Oracle
Oracle 中实现自增ID
Oracle 定时任务
Oracle 解锁临时表
Oracle 检查连接数
Oracle 表空间
Oracle 解释执行SQL
markdown作图(适用typora)
服务器压测
业务对象层和数据层
并发限流处理
中间件
Yarn的使用
Dubbo学习笔记-RPC扩展和本地Mock
Dubbo学习笔记-泛化实现进行mock
Redis缓存穿透,缓存击穿,缓存雪崩
Galera 集群说明
Pip 镜像
pip 使用
MySQL命令行
数据库缓存双写方案
Git相关操作
Redis 操作时间复杂度一览
nacos 杂记
mybatis 散记
shardingjdbc
一次线上事故排查发现的Caffeine缓存死锁问题
设计模式
重新讲讲单例模式和几种实现
更优雅地实现策略模式
Http-headers
Prometheus 杂散笔记
JAVA 散记
CompletableFuture
Gson、FastJson、Jackson、json-lib对比总结
jackson 时间的序列化踩坑
JVM
自定义注解
mysql类型和java类型 转换一览表
枚举维护一个Map<value, Enum>的映射
Java中String +、concat、StringBuilder、StringBuffer 性能对比
TraceId 使用
MySQL 多数据源处理
Mybatis-plus 流式查询
JAVA发送win 桌面通知
idea 启动项目失败非代码问题杂记
Lambda 简述
Arthas 使用笔记
一种链式更新数据的数据模式
Skywalking 新增中间件插件
Redission 使用
数据导出为图片
IDEA 的热重启
Netty 工具类
maven 插件
本文档使用 MrDoc 发布
-
+
首页
更优雅地实现策略模式
## 一、为什么讲策略模式 策略模式,应该是工作中比较常用的设计模式,调用方自己选择用哪一种策略完成对数据的操作,也就是“一个类的行为或其算法可以在运行时更改” 我个人的理解是 将一些除了过程不同其他都一样的函数封装成策略,然后调用方自己去选择想让数据执行什么过程策略。常见的例子为根据用户分类推荐不同的排行榜(用户关注点不一样,推荐榜单就不一样) 和[单例模式](https://www.cnblogs.com/hyry/p/16056069.html)一样,随着时间发展,我不再推荐经典策略模式,更推荐简单策略用枚举策略模式,复杂地用工厂策略模式。下面引入一个例子,我们的需求是:对一份股票数据列表,给出低价榜、高价榜、涨幅榜。这其中只有排序条件的区别,比较适合作为策略模式的例子 ## 二、经典策略模式 数据DTO ``` @Data public class Stock { // 股票交易代码 private String code; // 现价 private Double price; // 涨幅 private Double rise; } ``` 抽象得到的策略接口 ``` public interface Strategy { /** * 将股票列表排序 * * @param source 源数据 * @return 排序后的榜单 */ 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 { /** * dataService.getSource() 提供原始的股票数据 */ @Resource private DataService dataService; /** * 前端传入榜单类型, 返回排序完的榜单 * * @param rankType 榜单类型 * @return 榜单数据 */ 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 { /** * dataService.getSource() 提供原始的股票数据 */ @Resource private DataService dataService; /** * 前端传入榜单类型, 返回排序完的榜单 * * @param rankType 榜单类型 形似 RankEnum.HighPrice.name() * @return 榜单数据 */ public List<Stock> getRank(String rankType) { // 获取策略,这里如果未匹配会抛 IllegalArgumentException异常 RankEnum rank = RankEnum.valueOf(rankType); // 然后执行策略 return rank.sort(dataService.getSource()); } } ``` 可以看到,如果策略简单的话,基于枚举的策略模式优雅许多,调用方也做到了0修改,但正确地使用枚举策略模式需要额外考虑以下几点。 1. 枚举的策略类是公用且静态,这意味着这个策略过程不能引入非静态的部分,扩展性受限 2. 策略模式的目标之一,是优秀地扩展性和可维护性,最好能新增或修改某一策略类时,对其他类是无改动的。而枚举策略如果过多或者过程复杂,维护是比较困难的,可维护性受限 ## 四、基于工厂的策略模式 为了解决良好的扩展性和可维护性,我更推荐以下利用spring自带beanFactory的优势,实现一个基于工厂的策略模式。 策略类改动只是添加了@Service注解,并指定了Service的value属性 ``` /** * 高价榜 * 注意申明 Service.value = HighPrice,他是我们的key,下同 */ @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 { /** * dataService.getSource() 提供原始的股票数据 */ @Resource private DataService dataService; /** * 利用注解@Resource和@Autowired特性,直接获取所有策略类 * key = @Service的value */ @Resource private Map<String, Strategy> rankMap; /** * 前端传入榜单类型, 返回排序完的榜单 * * @param rankType 榜单类型 * @return 榜单数据 */ 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,也可以找找对应框架的工厂模式实现,或者自己实现一个抽象工厂 工厂策略模式会比枚举策略模式啰嗦,但也更加灵活、易扩展性和易维护。故简单策略推荐枚举策略模式,复杂策略才推荐工厂策略模式
寒烟濡雨
2022年3月28日 20:08
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
关于 MrDoc
觅思文档MrDoc
是
州的先生
开发并开源的在线文档系统,其适合作为个人和小型团队的云笔记、文档和知识库管理工具。
如果觅思文档给你或你的团队带来了帮助,欢迎对作者进行一些打赏捐助,这将有力支持作者持续投入精力更新和维护觅思文档,感谢你的捐助!
>>>捐助鸣谢列表
微信
支付宝
QQ
PayPal
Markdown文件
分享
链接
类型
密码
更新密码