Redian新闻
>
为什么我不再推荐枚举策略模式?

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

公众号新闻

点击上方“芋道源码”,选择“设为星标

管她前浪,还是后浪?

能浪的浪,才是好浪!

每天 10:33 更新文章,每天掉亿点点头发...

源码精品专栏

 
来源:toutiao.com/article/
7080135241830302212

一、为什么讲策略模式

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

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

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

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

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

二、经典策略模式

数据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());  
    }  
}  

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

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

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

三、基于枚举的策略模式

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

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

枚举策略类

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修改,但正确地使用枚举策略模式需要额外考虑以下几点。

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

四、基于工厂的策略模式

为了解决良好的扩展性和可维护性,我更推荐以下利用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 榜单类型 和Service注解的value属性一致  
     * @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,也可以找找对应框架的工厂模式实现,或者自己实现一个抽象工厂。

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



欢迎加入我的知识星球,一起探讨架构,交流源码。加入方式,长按下方二维码噢

已在知识星球更新源码解析如下:

最近更新《芋道 SpringBoot 2.X 入门》系列,已经 101 余篇,覆盖了 MyBatis、Redis、MongoDB、ES、分库分表、读写分离、SpringMVC、Webflux、权限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能测试等等内容。

提供近 3W 行代码的 SpringBoot 示例,以及超 4W 行代码的电商微服务项目。

获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

文章有帮助的话,在看,转发吧。

谢谢支持哟 (*^__^*)

微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
北上广已经卖疯了!楼市重回火热模式?马斯克的人体实验被FDA拒了!Neuralink大脑植入计划再推迟为什么我不建议你太乖如何高效优雅的使用java枚举谁有资格?什么模式?加州讨论赔偿非裔法案“对婚姻没有太多需求”:初婚年龄再推迟,女性更显著出生发$1000、每年打钱 美国国会再推"婴儿债券"立法为什么我不会再说“节哀顺变”?高达4.5万元的补助!美国婴儿出生就发$1000,每年都打钱!! 美国国会再推"婴儿债券"立法心结 (小小说)“妈妈,为什么我不能玩手机”,你的回答,可能会影响孩子一生大促营销心理学——为什么大促都用预售+尾款的促销方式?【大乡里出城】最近女明星怎么集体回春,究竟是有什么我不知道的!昨晚差点发生的意外,让我不再犹豫,赶快把它推荐给大家!为什么我不害怕被AI替代?刘润:到底什么是商业模式?为什么我们不再憧憬婚姻?出生发$1000、每年打钱,美国国会再推"婴儿债券"立法怎么开始学佛(五)佛教的两个问题电子报税,最快5天可退税,枚举扣除者,审查较费时为什么我不再推荐你使用 Alpine Linux 镜像气管插管如何选择呼吸机模式?3分钟快速入门!为什么我总是不开心?|测一测你的思维模式为什么我推荐你买一块可能一年就坏的烂固态?改编成电影片名就叫《炸北溪》「老铁」买单百亿新房,快手理想家如何跑通直播卖房新模式?为什么我不建议你买顶楼出生发$1000、每年打钱! 美国国会再推"婴儿债券"立法出生发$1000每年打钱,美国会再推“婴儿债券”立法再推秋禾房产,字节版「贝壳」呼之欲出?历史上房中术最厉害的女子,杨贵妃上榜,最后一位无人不服从社会化营销到数字化转型,神谷文化7周年如何书写新模式?“司机+导游” 成旅游新模式?如何管好人又管好车?英国红色双层巴士变绿色?!HS2铁路工程再推迟!警惕私下换汇/小红书租房诈骗!
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。