• 设计模式-行为型模式-策略模式


    一、什么是策略模式

            策略模式是一种行为设计模式,它允许在运行时选择算法或行为,并将其封装成独立的对象,使得这些算法或行为可以相互替换,而不影响使用它们的客户端。(ChatGPT生成)

            主要组成部分:

            1、策略(Strategy): 定义了一个算法族、行为或方法,并将其封装在一个接口或抽象类中,使得这些算法可以相互替换。

            2、具体策略(Concrete Strategy): 实现了策略接口,提供了具体的算法或行为实现。

            3、上下文(Context): 持有一个策略对象的引用,在运行时可动态切换不同的策略。

            原理:

            1、策略模式允许在运行时动态选择算法或行为,通过将具体的算法或行为封装成策略对象,然后将这些策略对象注入到上下文中。

            2、上下文根据不同的需求或条件选择合适的策略对象,并使用它执行特定的算法或行为。

            优点:

            1、易于扩展和维护: 可以方便地添加新的策略或修改现有策略,不影响原有代码。

            2、避免条件语句: 可以避免大量的条件语句,提高代码可读性和可维护性。

            3、代码复用: 可以通过共享策略对象来提高代码的复用性。

    二、场景模拟

            为了应用策略模式,我们模拟一个场景。在一个企业资源管理(ERP)系统中,可能会涉及到成本的计算,其中需要计算的成本例如:钢材、油漆、人工、运输等等,虽然说计算成本都是“单价*数量”,但是有些成本,例如钢材,不仅要计算钢材的成本,而且放置钢材或者其它费用,也需要进行一定的计费;对于人工成本,还需要计算企业担负的五险一金等。

     如上图所示,钢材是由吨数*单价计算的,但是要考虑保管费或其他费用,假设一吨n元,后续还可能考虑其它费用等;人工费用,雇员一人可能工资12000元,公司要负担其12%公积金或者医疗保险等;运输要考虑车辆维护费或者日常损耗。(以上仅是模拟,真实环境可能更加复杂或简单)。

    三、业务实现

    3.1、使用IF-ELSE方法

    1. public static BigDecimal useIfElse(String name, BigDecimal amount, BigDecimal price){
    2. if (name.equals("plate")) {
    3. // 单价×数量,再加上每吨200元的其它费用
    4. return amount.multiply(price).add(amount.multiply(new BigDecimal("200.00")));
    5. } else if (name.equals("paints")) {
    6. // 单价×数量,再加上每吨200元的其它费用
    7. return amount.multiply(price).add(amount.multiply(new BigDecimal("100.00")));
    8. } else if (name.equals("employer")) {
    9. // 人员×数量,再加上每吨200元的其它费用
    10. BigDecimal sum = amount.multiply(price); // 基本工资
    11. sum = sum.add(amount.multiply(price.multiply(new BigDecimal("0.12")))); // 加上额外支付的12%公积金
    12. return sum;
    13. } else if (name.equals("haulage")) {
    14. // 吨数×数量,再加上每吨300元的损耗费用
    15. return amount.multiply(price).add(amount.multiply(new BigDecimal("300.00")));
    16. } else {
    17. log.error("无法匹配");
    18. return new BigDecimal(0);
    19. }
    20. }

    我们用IF-ELSE方法,确实是可以实现功能,可以看出来很麻烦,看起来很混乱,没有结构,也没办法实现一些定制功能,后续变动起来也比骄困难,因为有一些东西都是写死的。我们来测试一下:

    1. public static void main(String[] args) {
    2. // 钢板单价及数量
    3. BigDecimal plateAmount = new BigDecimal(15);
    4. BigDecimal platePrice = new BigDecimal("888.88");
    5. // 油漆价格及数量
    6. BigDecimal paintsAmount = new BigDecimal(20);
    7. BigDecimal paintsPrice = new BigDecimal("490.00");
    8. // 雇员工资及人数
    9. BigDecimal employerAmount = new BigDecimal(10);
    10. BigDecimal employerPrice = new BigDecimal("10000.00");
    11. // 运输价格及吨数
    12. BigDecimal haulageAmount = new BigDecimal(25);
    13. BigDecimal haulagePrice = new BigDecimal("3000.00");
    14. // 计算各个成本
    15. BigDecimal plate = useIfElse("plate", plateAmount, platePrice);
    16. BigDecimal paints = useIfElse("paints", paintsAmount, paintsPrice);
    17. BigDecimal employer = useIfElse("employer", employerAmount, employerPrice);
    18. BigDecimal haulage = useIfElse("haulage", haulageAmount, haulagePrice);
    19. BigDecimal sum = plate.add(paints).add(employer).add(haulage);
    20. log.info("钢材:{}元,油漆{}元,雇员:{}元,运输:{}元,总计:{}元", plate, paints, employer, haulage, sum);
    21. }

    可以看到确实正确的计算出了结果,但是这种方式不具有拓展性,随着程序功能的增加,会越来越混乱。那有些同学可能会说,为什么不用Switch-Case方法?那就来实践一下。

    3.2、使用Switch-Case方法

    1. public static BigDecimal useSwitch(String name, BigDecimal amount, BigDecimal price){
    2. switch (name) {
    3. case "plate":
    4. // 单价×数量,再加上每吨200元的其它费用
    5. return amount.multiply(price).add(amount.multiply(new BigDecimal("200.00")));
    6. case "paints":
    7. // 单价×数量,再加上每吨200元的其它费用
    8. return amount.multiply(price).add(amount.multiply(new BigDecimal("100.00")));
    9. case "employer":
    10. // 人员×数量,再加上每吨200元的其它费用
    11. BigDecimal sum = amount.multiply(price); // 基本工资
    12. sum = sum.add(amount.multiply(price.multiply(new BigDecimal("0.12")))); // 加上额外支付的12%公积金
    13. return sum;
    14. case "haulage":
    15. // 吨数×数量,再加上每吨300元的损耗费用
    16. return amount.multiply(price).add(amount.multiply(new BigDecimal("300.00")));
    17. default:
    18. log.error("无法匹配");
    19. return new BigDecimal(0);
    20. }
    21. }

    使用Switch-Case方法后,确实代码变得比较清晰了,但是一些东西仍然是写死的,不具备很好的扩展性。

    3.3、使用策略模式重构代码

            其中ICosts是一个接口,里面只有一个方法,就是成本的计算。他的实现类包含钢材、油漆、人工等等,可以有很多。Context是策略控制类,外部可以传递不同策略执行计算成本方法。

     3.3.1、编写成本计算接口

    1. public interface ICosts {
    2. /**
    3. * 成本计算
    4. * @param amount 数量
    5. * @param price 单价
    6. * @param other 其它信息
    7. * @return 成本金额
    8. */
    9. BigDecimal calculateCosts(T amount, BigDecimal price, Map other);
    10. }

     解释一下amount为什么要用T表示,因为这里数量不仅仅是BigDecimal形式的,可能会涉及到人、车辆等等,这种是没有小数点的……;关于other就是一些附加信息,例如钢材的除锈费用、维护费用,人员的五险一金比例等等。

    3.3.2、具体成本实现类

    钢材

    1. public class GCCosts implements ICosts {
    2. /*
    3. * 钢材成本
    4. * 1、基本成本:吨数 * 价格;
    5. * 2、存放成本:吨数 * 存放消费;
    6. * 3、防锈成本:吨数 * 防锈成本;
    7. * */
    8. @Override
    9. public BigDecimal calculateCosts(BigDecimal amount, BigDecimal price, Map other) {
    10. BigDecimal sum = amount.multiply(price);
    11. // 计算存放成本
    12. if (other.containsKey("CF")) {
    13. sum = sum.add(amount.multiply(other.get("CF")));
    14. }
    15. // 计算防锈成本
    16. if (other.containsKey("CX")) {
    17. sum = sum.add(amount.multiply(other.get("CX")));
    18. }
    19. return sum;
    20. }
    21. }

    油漆

    1. public class YQCosts implements ICosts {
    2. /*
    3. * 油漆成本
    4. * 1、基础成本:单价 * 数量;
    5. * 2、存放成本: 数量 * 存放费用;
    6. * */
    7. @Override
    8. public BigDecimal calculateCosts(BigDecimal amount, BigDecimal price, Map other) {
    9. BigDecimal sum = amount.multiply(price);
    10. if (other.containsKey("CF")) {
    11. sum = sum.add(amount.multiply(other.get("CF")));
    12. }
    13. return sum;
    14. }
    15. }

    人工

    1. public class RGCosts implements ICosts {
    2. /*
    3. * 人工成本:
    4. * 1、基础成本:人数 * 工资;
    5. * 2、公积金成本: 人数 * (工资 * 公积金比例);
    6. * 3、五险成本:人数 * (工资 * 企业分摊比例);
    7. * */
    8. @Override
    9. public BigDecimal calculateCosts(Long amount, BigDecimal price, Map other) {
    10. // 先计算每个人成本
    11. BigDecimal personCosts = price;
    12. // 加公积金
    13. if (other.containsKey("GJJ")) {
    14. personCosts = personCosts.add(price.multiply(other.get("GJJ")));
    15. }
    16. // 加五险
    17. if (other.containsKey("BX")) {
    18. personCosts = personCosts.add(price.multiply(other.get("BX")));
    19. }
    20. return personCosts.multiply(BigDecimal.valueOf(amount)); // 计算总成本
    21. }
    22. }

    运输

    1. public class YSCosts implements ICosts {
    2. /*
    3. * 运输成本:
    4. * 1、基础成本:运输数量 * 运输单价;
    5. * 2、损耗费用:例如换机油,养护等费用,固定金额(模拟);
    6. * */
    7. @Override
    8. public BigDecimal calculateCosts(BigDecimal amount, BigDecimal price, Map other) {
    9. BigDecimal sum = amount.multiply(price);
    10. // 加上固定的养护费用
    11. if (other.containsKey("YH")) {
    12. sum = sum.add(other.get("YH"));
    13. }
    14. return sum;
    15. }
    16. }

    3.3.3、成本策略控制类

    1. public class CostsContext{
    2. private ICosts iCosts;
    3. public CostsContext(ICosts iCosts) {
    4. this.iCosts = iCosts;
    5. }
    6. public BigDecimal calculateCosts(T amount, BigDecimal price, Map other) {
    7. return iCosts.calculateCosts(amount, price, other);
    8. }
    9. }

    此类充当一个调度作用,不同的成本计算就选择不同的实现类,然后执行calculateCosts()方法。

    3.3.4、测试

    1. public static void main(String[] args) {
    2. // 钢板单价及数量
    3. BigDecimal plateAmount = new BigDecimal(15);
    4. BigDecimal platePrice = new BigDecimal("888.88");
    5. Map plateMap = new HashMap(){{
    6. put("CF", new BigDecimal("300"));
    7. put("CX", new BigDecimal("200"));
    8. }};
    9. // 油漆价格及数量
    10. BigDecimal paintsAmount = new BigDecimal(20);
    11. BigDecimal paintsPrice = new BigDecimal("490.00");
    12. Map paintsMap = new HashMap(){{
    13. put("CF", new BigDecimal("300"));
    14. }};
    15. // 雇员工资及人数
    16. // BigDecimal employerAmount = new BigDecimal(10);
    17. Long employerAmount = 10L;
    18. BigDecimal employerPrice = new BigDecimal("10000.00");
    19. Map employerMap = new HashMap(){{
    20. put("GJJ", new BigDecimal("0.12"));
    21. put("BX", new BigDecimal("0.06"));
    22. }};
    23. // 运输价格及吨数
    24. BigDecimal haulageAmount = new BigDecimal(25);
    25. BigDecimal haulagePrice = new BigDecimal("3000.00");
    26. Map haulageMap = new HashMap(){{
    27. put("YH", new BigDecimal("1500"));
    28. }};
    29. // 使用策略模式计算
    30. // 钢材
    31. CostsContext plateCost = new CostsContext<>(new GCCosts());
    32. BigDecimal plate = plateCost.calculateCosts(plateAmount, platePrice, plateMap);
    33. // 油漆
    34. CostsContext paintsCost = new CostsContext<>(new YQCosts());
    35. BigDecimal paints = paintsCost.calculateCosts(paintsAmount, paintsPrice, paintsMap);
    36. // 人工
    37. CostsContext employerCost = new CostsContext<>(new RGCosts());
    38. BigDecimal employer = employerCost.calculateCosts(employerAmount, employerPrice, employerMap);
    39. // 运输
    40. CostsContext haulageCost = new CostsContext<>(new YQCosts());
    41. BigDecimal haulage = haulageCost.calculateCosts(haulageAmount, haulagePrice, haulageMap);
    42. BigDecimal sum = plate.add(paints).add(employer).add(haulage);
    43. log.info("钢材:{}元,油漆{}元,雇员:{}元,运输:{}元,总计:{}元", plate, paints, employer, haulage, sum);
    44. }

    上面是一些各种成本的数量和单价信息,同时使用Map存放了一些特有的成本信息,然后下面我们使用成本策略控制类CostsContext装载不同的实现类来进行成本的计算。

    可以看到成功的计算出了结果。我们不仅实现了功能,也为后续的扩展打下了良好的基础,例如后续加新的成本类型进行计算,我们只需要继承ICosts实现不同的方法即可,如果某一个类型的计算加了一些条件,这样我们直接在Map里面添加条件,并在相应的实体类进行处理即可,这样让我们的代码结构变得清晰。

    四、小结 

            if-else是我们初学代码时就接触到的代码,伴随我们走过了很多年,但是我们的思维一定要有提高,不要一有需求,就想到if-else,确实,if-else可以快速地实现功能,我们不可否认,但是随着项目复杂度的提高,if-else已经无法满足我们的需求,它会让我们的代码变得臃肿、复杂、结构混乱,所以我们就想到要用其它方法来替代if-else,让我们整个代码结构变得清晰。

            上面我们介绍了使用策略模式来代替一堆的if-else,策略模式的优点远不止如此:

            1、具有强的扩展性,例如我们扩充新的成本计算,只需要新建类并实现接口即可;

            2、易维护和理解,如何我么要看或者修改钢材的成本计算,我们不用到一堆if-else里面去找具体的代码块,我们只需要进去相应的实现类去看就可以,代码清晰明了,没有其他代码的干扰(体现出隔离性),我们维护起来也得心应手;

            3、使用策略模式可以让我们代码变得优美,思维能力和架构水平得到提升。

  • 相关阅读:
    晴空一“鹤”排“云”上:以数为翅的中国飞鹤
    Reading Note(10)——AutoBridge
    基于springboot实现贸易行业crm系统项目【项目源码+论文说明】
    SpringBoot运维实用篇、打包、运行、高级配置、多环境开发、日志
    设计模式之桥接模式
    设计模式-中介者模式
    react评论列表连接数据库
    docker 搭建私有仓库并发布私有镜像
    【Windows】Edge浏览器自动更新服务启用选禁用被拒绝访问的解决方案
    TypeScript 基础类型
  • 原文地址:https://blog.csdn.net/Hao_ge_666/article/details/134491818