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

这篇具有很好参考价值的文章主要介绍了为什么我不再推荐枚举策略模式。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

  • 一、为什么讲策略模式

  • 二、经典策略模式

  • 三、基于枚举的策略模式

  • 四、基于工厂的策略模式


一、为什么讲策略模式

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

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

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

基于 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,也可以找找对应框架的工厂模式实现,或者自己实现一个抽象工厂。

工厂策略模式会比枚举策略模式啰嗦,但也更加灵活、易扩展性和易维护。故简单策略推荐枚举策略模式,复杂策略才推荐工厂策略模式。文章来源地址https://www.toymoban.com/news/detail-467880.html

到了这里,关于为什么我不再推荐枚举策略模式的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 记录--为什么推荐用svg而不用icon?

    使用背景: 1.因为svg图标在任何设备下都可以高清显示,不会模糊。而icon会在显卡比较低的电脑上有显示模糊的情况 2.svg图标在页面render时 速度会比icon稍微快一点 3.实现小程序换肤功能 ;方案见:www.yuque.com/lufeilizhix… SVG基础可参考:www.yuque.com/lufeilizhix… inline svg是目前前

    2024年02月08日
    浏览(44)
  • 为什么js中不推荐使用eval函数

    \\\'eval\\\'函数是javascript中的一个内置函数,它的主要作用是将传入的字符串作为代码来执行。换句话说,\\\'eval\\\'可以将动态生成的字符串当作javascript代码来执行,并返回执行结果。 我的理解就是它可以执行传入的代码,并返回执行结果。 \\\'eval\\\'可以执行任何传入的字符串,所以意味

    2024年02月08日
    浏览(46)
  • 为什么不推荐使用Lombok?@Data不香吗?

    目录 一、前言 二、源码跟踪 三、总结 之前写项目遇到的一个Bug,下面是模拟代码。 新建一个springboot的项目,Person一个实体类,定义一个方法传一个JSON数据 springboot启动之后postman发送一次请求。 请求路径:http://localhost:8080/user JSON数据: 后台输出结果 我们会发现,aName字段

    2024年02月09日
    浏览(45)
  • [Rust笔记] 为什么Rust英文文档普遍将【枚举值】记作variant而不是enum value?

    在阅读各类 Rust 英文技术资料时,你是否也曾经困惑过:为何每逢【枚举值】的概念出现时,作者都会以 variant 一词指代之?就字面含义而言, enum value 岂不是更贴切与易理解。简单地讲,这馁馁地是 Rust 技术优越性·宣传软文的广告梗,而且是很高端的内行梗。 Rustacean 们看

    2023年04月08日
    浏览(38)
  • 【数学建模】为什么存在最优策略?

            在进行优化回归过程,首先要看看是否存在最优策略?         在有限马尔可夫决策过程 (MDP) 中,最优策略被定义为同时最大化所有状态值的策略¹。换句话说,如果存在最优策略,则最大化状态  s  值的策略与最大化状态 值的策略相同 。 ²   但为什么

    2024年02月16日
    浏览(32)
  • 为什么不推荐在头文件中直接定义函数?

    在C++中,函数的分文件编写是一种让代码结构更加清晰的方法,通常可以分为以下几个步骤: 创建后缀名为 .h 的头文件,在头文件中写函数的声明,以及可能用到的其他头文件或命名空间 创建后缀名为 .cpp 的源文件,在源文件中写函数的定义,同时引入自定义头文件,将头

    2024年02月16日
    浏览(44)
  • 【Spring】浅谈spring为什么推荐使用构造器注入

    因本人实力有限,该文章主要内容(在文章基础上加了点点东西)均来自: 原文链接:https://www.cnblogs.com/joemsu/p/7688307.html 作者:joemsu ​ Spring框架对Java开发的重要性不言而喻,其核心特性就是IOC(Inversion of Control, 控制反转)和AOP,平时使用最多的就是其中的IOC,我们通过

    2024年02月13日
    浏览(37)
  • Vue3为什么推荐使用ref而不是reactive

    reactive 本身具有很大局限性导致使用过程需要额外注意,如果忽视这些问题将对开发造成不小的麻烦;ref更像是vue2时代 option api 的 data 的替代,可以存放任何数据类型,而 reactive 声明的数据类型只能是对象; 先抛出结论,再详细说原因:非必要不用 reactive ! (官方文档也有对应的推荐

    2024年02月07日
    浏览(37)
  • 编程开发8大语言详解,为什么Java是我最推荐的?

    很多没有接触过编程语言的同学,都会觉得编程开发特别高端和神奇,担心理解不了更担心学不会。 当然,也有人会认为,你既然是做编程的,那么你应该什么都会,什么软件的开发都能完成,这是平哥经常听到的两种声音。 在此,平哥需要给大家科普一下, 编程确实改变

    2024年02月05日
    浏览(61)
  • 为什么Spring和IDEA不推荐使用@Autowired注解,有哪些替代方案?

    在使用Spring框架和JetBrains IDEA集成开发环境(IDE)进行Java开发时,你可能经常会遇到@Autowired注解。@Autowired是Spring框架中用于实现依赖注入的核心注解之一。然而,近年来,Spring和IDEA都不再推荐使用@Autowired注解,并提出了更好的替代方案。本文将详细分析为什么Spring和IDEA不

    2024年02月07日
    浏览(50)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包