400-035-6699
当前位置: 首页 » 技术支持 » 博文资讯 »

为什么放弃枚举策略模式?探讨替代方案的优势

策略模式是一种设计模式,它允许在运行时选择和切换算法,从而提供灵活性和可扩展性。在软件开发中,经常会有多种不同的处理方式来应对相似的问题,比如排序、过滤、验证等,这时,策略模式就派上用场了。
下面我们将通过一个例子来详细解释策略模式的概念和使用方法
假设我们有一个在线购物网站,用户下单时需要选择支付方式。我们提供了多种支付方式,如支付宝、微信支付、信用卡支付等。用户在下单时可以根据自己的喜好选择其中一种支付方式。这里就可以应用策略模式。
首先,我们定义一个策略接口,它包含了一个方法:`pay`,用于执行支付操作。然后,我们为每种支付方式创建一个实现了这个接口的策略类,比如`AlipayStrategy`、`WechatPayStrategy`、`CreditCardPayStrategy`等。每个策略类都实现了`pay`方法,但实现细节不同,因为每种支付方式的具体实现不同。
接下来,我们创建一个上下文类`Context`,它包含一个策略对象的引用。`Context`类的目的是使用这个策略对象来执行支付操作。客户端可以创建一个`Context`对象,并将其与任何实现了`PayStrategy`接口的策略对象关联起来。
这样,客户端就可以通过改变上下文类中策略对象的引用来改变支付方式,而无需修改其他代码,这就实现了策略模式的开闭原则:对扩展开放,对修改封闭。
使用策略模式的好处是可以让算法的变化独立于使用算法的客户。这提高了代码的灵活性和可维护性,同时也使得算法可以独立于使用它的客户端进行独立测试。
总的来说,策略模式是一种非常有用的设计模式,它可以帮助我们更好地组织代码,提高代码的可维护性和可扩展性。在实际应用中,我们可以根据实际情况选择合适的策略模式实现方式,比如简单策略用枚举策略模式,复杂策略用工厂策略模式等。

为什么放弃枚举策略模式?探讨替代方案的优势


一、为什么讲策略模式

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

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

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

基于 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排序后的榜单 */
List
    
     sort
     (List
      
       source)
      
    ;
}

实现我们的策略类

/** *高价榜 */
publicclassHighPriceRankimplementsStrategy{

@Override
publicList
    
     sort
     (List
      
       source)
      
    {
returnsource.stream()
.sorted(Comparator.comparing(Stock::getPrice).reversed())
.colLECt(Collectors.toList());
}
}

/** *低价榜 */
publicclassLowPriceRankimplementsStrategy{

@Override
publicList
    
     sort
     (List
      
       source)
      
    {
returnsource.stream()
.sorted(Comparator.comparing(Stock::getPrice))
.collect(Collectors.toList());
}
}

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

@Override
publicList
    
     sort
     (List
      
       source)
      
    {
returnsource.stream()
.sorted(Comparator.comparing(Stock::getRise).reversed())
.collect(Collectors.toList());
}
}

经典的Context类,

publicclassContext{
privateStrategystrategy;

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

publicList
    
     getRank
     (List
      
       source)
      
    {
returnstrategy.sort(source);
}
}

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

@Service
publicclassRankServiceImpl{

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

/** *前端传入榜单类型,返回排序完的榜单 * *@paramrankType榜单类型 *@return榜单数据 */
publicList
    
     getRank
     (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
publicList
    
     sort
     (List
      
       source)
      
    {
returnsource.stream()
.sorted(Comparator.comparing(Stock::getPrice).reversed())
.collect(Collectors.toList());
}
},
LowPrice{
@Override
publicList
    
     sort
     (List
      
       source)
      
    {
returnsource.stream()
.sorted(Comparator.comparing(Stock::getPrice))
.collect(Collectors.toList());
}
},
HighRise{
@Override
publicList
    
     sort
     (List
      
       source)
      
    {
returnsource.stream()
.sorted(Comparator.comparing(Stock::getRise).reversed())
.collect(Collectors.toList());
}
};

//这里定义了策略接口
publicabstractList
    
     sort
     (List
      
       source)
      
    ;
}

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

@Service
publicclassRankServiceImpl{

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

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

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

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

四、基于工厂的策略模式

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

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

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

@Override
publicList
    
     sort
     (List
      
       source)
      
    {
returnsource.stream()
.sorted(Comparator.comparing(Stock::getPrice).reversed())
.collect(Collectors.toList());
}
}

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

@Override
publicList
    
     sort
     (List
      
       source)
      
    {
returnsource.stream()
.sorted(Comparator.comparing(Stock::getPrice))
.collect(Collectors.toList());
}
}

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

@Override
publicList
    
     sort
     (List
      
       source)
      
    {
returnsource.stream()
.sorted(Comparator.comparing(Stock::getRise).reversed())
.collect(Collectors.toList());
}
}

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

@Service
publicclassRankServiceImpl{

/** *dataService.getSource()提供原始的股票数据 */
@Resource
privateDataServicedataService;
/** *利用注解@Resource@Autowired特性,直接获取所有策略类 *key=@Service的value */
@Resource
privateMap
   
    rankMap; 
    /** *前端传入榜单类型,返回排序完的榜单 * *@paramrankType榜单类型和Service注解的value属性一致 *@return榜单数据 */ 
    publicList
     
      getRank
      (StringrankType)
     { 
    //判断策略是否存在 
    if(!rankMap.cont
    ainsKey(rankType)){ 
    throw
    newIllegalArgumentException(
    "rankTypenotfound"); } 
    //获得策略实例 Strategyrank=rankMap.get(rankType); 
    //执行策略 
    returnrank.sort(dataService.getSource()); } } 
   

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

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



审核编辑 :李倩



【限时免费】一键获取网络规划系统模板+传输架构设计+连通性评估方案

相关文章

服务电话:
400-035-6699
企服商城