0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

为什么我不再推荐枚举策略模式?

jf_ro2CN3Fa 来源:芋道源码 2023-04-14 10:52 次阅读


一、为什么讲策略模式

策略模式,应该是工作中比较常用的设计模式,调用方自己选择用哪一种策略完成对数据的操作,也就是“一个类的行为或其算法可以在运行时更改”

我个人的理解是 将一些除了过程不同其他都一样的函数封装成策略,然后调用方自己去选择想让数据执行什么过程策略。常见的例子为根据用户分类推荐不同的排行榜(用户关注点不一样,推荐榜单就不一样)

和单例模式一样,随着时间发展,我不再推荐经典策略模式,更推荐简单策略用枚举策略模式,复杂地用工厂策略模式。下面引入一个例子,我们的需求是:对一份股票数据列表,给出低价榜、高价榜、涨幅榜。这其中只有排序条件的区别,比较适合作为策略模式的例子

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 视频教程:https://doc.iocoder.cn/video/

二、经典策略模式

数据DTO

@Data
publicclassStock{

//股票交易代码
privateStringcode;

//现价
privateDoubleprice;

//涨幅
privateDoublerise;
}

抽象得到的策略接口

publicinterfaceStrategy{

/**
*将股票列表排序
*
*@paramsource源数据
*@return排序后的榜单
*/
Listsort(Listsource);
}

实现我们的策略类

/**
*高价榜
*/
publicclassHighPriceRankimplementsStrategy{

@Override
publicListsort(Listsource){
returnsource.stream()
.sorted(Comparator.comparing(Stock::getPrice).reversed())
.collect(Collectors.toList());
}
}

/**
*低价榜
*/
publicclassLowPriceRankimplementsStrategy{

@Override
publicListsort(Listsource){
returnsource.stream()
.sorted(Comparator.comparing(Stock::getPrice))
.collect(Collectors.toList());
}
}

/**
*高涨幅榜
*/
publicclassHighRiseRankimplementsStrategy{

@Override
publicListsort(Listsource){
returnsource.stream()
.sorted(Comparator.comparing(Stock::getRise).reversed())
.collect(Collectors.toList());
}
}

经典的Context类,

publicclassContext{
privateStrategystrategy;

publicvoidsetStrategy(Strategystrategy){
this.strategy=strategy;
}

publicListgetRank(Listsource){
returnstrategy.sort(source);
}
}

于是 我们顺礼成章地得到调用类--榜单实例RankServiceImpl

@Service
publicclassRankServiceImpl{

/**
*dataService.getSource()提供原始的股票数据
*/
@Resource
privateDataServicedataService;

/**
*前端传入榜单类型,返回排序完的榜单
*
*@paramrankType榜单类型
*@return榜单数据
*/
publicListgetRank(StringrankType){
//创建上下文
Contextcontext=newContext();
//这里选择策略
switch(rankType){
case"HighPrice":
context.setStrategy(newHighPriceRank());
break;
case"LowPrice":
context.setStrategy(newLowPriceRank());
break;
case"HighRise":
context.setStrategy(newHighRiseRank());
break;
default:
thrownewIllegalArgumentException("rankTypenotfound");
}
//然后执行策略
returncontext.getRank(dataService.getSource());
}
}

我们可以看到经典方法,创建了一个接口、三个策略类,还是比较啰嗦的。调用类的实现也待商榷,新增一个策略类还要修改榜单实例(可以用抽象工厂解决,但是复杂度又上升了)。加之我们有更好的选择,所以此处不再推荐经典策略模式

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

  • 项目地址:https://github.com/YunaiV/yudao-cloud
  • 视频教程:https://doc.iocoder.cn/video/

三、基于枚举的策略模式

这里对这种简单的策略,推荐用枚举进行优化。枚举的本质是创建了一些静态类的集合。

我下面直接给出例子,大家可以直观感受一下

枚举策略类

publicenumRankEnum{
//以下三个为策略实例
HighPrice{
@Override
publicListsort(Listsource){
returnsource.stream()
.sorted(Comparator.comparing(Stock::getPrice).reversed())
.collect(Collectors.toList());
}
},
LowPrice{
@Override
publicListsort(Listsource){
returnsource.stream()
.sorted(Comparator.comparing(Stock::getPrice))
.collect(Collectors.toList());
}
},
HighRise{
@Override
publicListsort(Listsource){
returnsource.stream()
.sorted(Comparator.comparing(Stock::getRise).reversed())
.collect(Collectors.toList());
}
};

//这里定义了策略接口
publicabstractListsort(Listsource);
}

对应的调用类也得以优化,榜单实例RankServiceImpl

@Service
publicclassRankServiceImpl{

/**
*dataService.getSource()提供原始的股票数据
*/
@Resource
privateDataServicedataService;

/**
*前端传入榜单类型,返回排序完的榜单
*
*@paramrankType榜单类型形似RankEnum.HighPrice.name()
*@return榜单数据
*/
publicListgetRank(StringrankType){
//获取策略,这里如果未匹配会抛IllegalArgumentException异常
RankEnumrank=RankEnum.valueOf(rankType);
//然后执行策略
returnrank.sort(dataService.getSource());
}
}

可以看到,如果策略简单的话,基于枚举的策略模式优雅许多,调用方也做到了0修改,但正确地使用枚举策略模式需要额外考虑以下几点。

  • 枚举的策略类是公用且静态,这意味着这个策略过程不能引入非静态的部分,扩展性受限
  • 策略模式的目标之一,是优秀的扩展性和可维护性,最好能新增或修改某一策略类时,对其他类是无改动的。而枚举策略如果过多或者过程复杂,维护是比较困难的,可维护性受限

四、基于工厂的策略模式

为了解决良好的扩展性和可维护性,我更推荐以下利用spring自带beanFactory的优势,实现一个基于工厂的策略模式。

策略类改动只是添加了@Service注解,并指定了Service的value属性

/**
*高价榜
*注意申明Service.value=HighPrice,他是我们的key,下同
*/
@Service("HighPrice")
publicclassHighPriceRankimplementsStrategy{

@Override
publicListsort(Listsource){
returnsource.stream()
.sorted(Comparator.comparing(Stock::getPrice).reversed())
.collect(Collectors.toList());
}
}

/**
*低价榜
*/
@Service("LowPrice")
publicclassLowPriceRankimplementsStrategy{

@Override
publicListsort(Listsource){
returnsource.stream()
.sorted(Comparator.comparing(Stock::getPrice))
.collect(Collectors.toList());
}
}

/**
*高涨幅榜
*/
@Service("HighRise")
publicclassHighRiseRankimplementsStrategy{

@Override
publicListsort(Listsource){
returnsource.stream()
.sorted(Comparator.comparing(Stock::getRise).reversed())
.collect(Collectors.toList());
}
}

调用类修改较大,接入借助spring工厂特性,完成策略类

@Service
publicclassRankServiceImpl{

/**
*dataService.getSource()提供原始的股票数据
*/
@Resource
privateDataServicedataService;
/**
*利用注解@Resource@Autowired特性,直接获取所有策略类
*key=@Service的value
*/
@Resource
privateMaprankMap;

/**
*前端传入榜单类型,返回排序完的榜单
*
*@paramrankType榜单类型和Service注解的value属性一致
*@return榜单数据
*/
publicListgetRank(StringrankType){
//判断策略是否存在
if(!rankMap.containsKey(rankType)){
thrownewIllegalArgumentException("rankTypenotfound");
}
//获得策略实例
Strategyrank=rankMap.get(rankType);
//执行策略
returnrank.sort(dataService.getSource());
}
}

若读者使用的不是Spring,也可以找找对应框架的工厂模式实现,或者自己实现一个抽象工厂。

工厂策略模式会比枚举策略模式啰嗦,但也更加灵活、易扩展性和易维护。故简单策略推荐枚举策略模式,复杂策略才推荐工厂策略模式。



审核编辑 :李倩



声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 框架
    +关注

    关注

    0

    文章

    403

    浏览量

    17508
  • 函数
    +关注

    关注

    3

    文章

    4333

    浏览量

    62708

原文标题:为什么我不再推荐枚举策略模式?

文章出处:【微信号:芋道源码,微信公众号:芋道源码】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    Java 枚举策略模式、函数式接口的结合:实现高内聚低耦合的设计

    中,通常会使用枚举来定义业务上的一组常量,那除了简单地定义常量之外,我们如何利用枚举来实现高内聚、低耦合的设计呢?下面介绍下枚举策略模式
    的头像 发表于 11-21 14:06 228次阅读

    请问什么情况下会损坏TLV2548,或者导致INT信号不再反应?

    如题,请问什么情况下会损坏TLV2548,或者导致INT信号不再反应? 曾多次驱动过该AD,都可以正常读取码值。但有两次遇到过AD没有任何反应,只在上电的瞬间读取电压,INT不再拉低(
    发表于 11-14 06:39

    苹果调整策略:逐步摒弃年更产品发布模式

    10月8日讯,彭博社知名记者马克·古尔曼在《Power On》通讯中揭示,苹果公司正逐步转变其传统的“年度更新”发布模式,迈向更为灵活的产品发布策略
    的头像 发表于 10-08 16:46 807次阅读

    stm32f407 USB外接HUB怎么枚举HUB和其他USB设备?

    stm32f407 的USB接口 想外接一个HUB 来支持更多的USB设备,但是这个HUB 该怎么枚举,还有就是HUB下的USB设备怎么枚举,有做过的大神吗。求指导,求demo。小弟拜谢了
    发表于 04-29 08:13

    STM32F103 USB枚举不成功的原因?

    (NVIC_VectTab_FLASH,0x8800),但此时USB便枚举不成功,此时其他中断是正常的;但若把APP起始地址改为0X8000000不使用IAP进行跳转则枚举没有问题.比较怀疑中断向量这块的设置是不是还有问题,跪
    发表于 04-29 06:29

    stm32f103 usb枚举问题求解

    移植了一个USB HID设备,发现必须初始化usart1,这样usb hid设备才能枚举,否则枚举失败,没有发现硬件上有联系啊?
    发表于 04-26 07:57

    调试USB hots的时候,枚举过不去,为什么?

    调试USB hots的时候,枚举过不去,第一步的状态都不对。看数据是中断函数USBH_OTG_ISR_Handler返回的,请问哪位有相关说明发一下,现在总是触发gintsts.b.sofintr中断。谢谢!
    发表于 04-10 07:28

    CYUS3014 RAM烧写成功,为什么枚举不起来?

    CYUSB3014 RAM显示烧写成功后,缺不再枚举,技术支持让更换芯片后,问题依旧,请问是否还有别的方式可以查找问题?
    发表于 02-29 07:13

    cyusb3014枚举的工作原理是什么?如果下载固件后不枚举,可能会是什么原因,原理是什么?

    用3014设计自己的板子,参考开发板kit3的原理图,插入计算机后可以被识别为Bootloader,采用USB启动模式,下载实例中的固件后,显示烧写成功,但设备在计算机中消失了,不再重新被
    发表于 02-29 07:11

    通常情况下CYUSB3014枚举后的结果都会显示为Bootloader,如何改变枚举结果?

    通常情况下CYUSB3014枚举后的结果都会显示为Bootloader 如果向让USB3.0连接以后,将我连接的设备识别成ChinaBeijing .请问我应该如何操作??
    发表于 02-28 08:01

    如何同时枚举SlaveFIFO和UART(CDC)?

    使用Re: Slave FIFO + UART Driver Setup中的程序,将img下载进FX3中,成功枚举出了“USB串行设备(COM13)”,但是并没有出现
    发表于 02-28 07:23

    如何在SlaveFifoSync例程中添加HID设备枚举

    您好,使用了SlaveFifoSync的32bit模式例程,现在想在其中添加一个HID的设备(鼠标),程序见附件,参照了Can EZ-USB FX3 enumerate seri
    发表于 02-28 06:31

    cx3初始化GPIO会停止UVC枚举的原因?

    需要使用 GPIO 为的图像传感器和其他设备进行重置控制。 为此,正在尝试在的 CSI 生成的代码中添加简单的 GPIO 控制,但是当我添加以下几行时,
    发表于 02-27 07:38

    cyusb3014进入DP替代模式后断开USB连接是什么原因?

    使用USB3.0 + dp2lane mode),请问可能是什么原因吗? 有两个猜测: 1. 检查 usb3.0+dp2lane 后是否会再次枚举USB模式已协商。 枚举失败 2
    发表于 02-23 08:17

    枚举有多大?c语言枚举end的作用是什么?

    枚举有多大?c语言枚举end的作用是什么? 枚举在C语言中是一种常见的数据类型,用于定义一组相互关联的常量或者变量。它通常用于表示一系列可能的取值,使得程序更加易读和易维护。在C语言中,枚举
    的头像 发表于 01-19 14:19 626次阅读