鸣谢:《大话设计模式》

简介

策略模式是一种行为型设计模式,它允许在运行时动态选择算法的行为,使得一个类的行为或算法可以在不修改其结构的情况下发生变化。策略模式将每个算法封装成独立的类,使得这些算法可以相互替换,而不会影响到客户端代码。

说人话:
– 去掉繁杂的if else,(这里例子用的时switch, 来减少多个if判断,可以提高性能,当然可以用反射来去除switch,这个是后话,重要的是思想哈!)

  • 策略模式封装了变化。

  • 修改其中任何一个算法不会影响其他的算法。

主要组成部分:

  • 上下文(Context):上下文是包含一个策略对象的类。上下文负责维护一个对策略对象的引用,并在需要时调用策略对象执行具体的算法。

  • 策略接口(Strategy Interface):这是一个接口或抽象类,定义了所有具体策略类必须实现的方法。通常,这些方法代表了不同的算法或策略。

  • 具体策略类(Concrete Strategy Classes):这些类实现了策略接口,每个具体策略类代表了一个不同的算法或策略。它们提供了特定算法的实现。

工作流程

  • 客户端创建一个上下文对象,并根据需要选择一个具体的策略对象,然后将该策略对象传递给上下文。

  • 当上下文需要执行特定算法时,它会调用策略对象的方法,而不是自己实现该算法。

  • 上下文对象可以在运行时动态切换不同的策略对象,从而改变其行为,而不影响其内部结构。

代码

1.普通策略模式

1.1结构

1.2代码

1.2.1策略类或接口
/**
 * @author: lpy
 * @Date: 2023/09/13
 * @desc: 策略
 */
public abstract class Strategy {
    /**
     * 策略类, 所有支持的算法的公共接口
     * @param price
     * @param num
     * @return
     */
    public abstract double acceptCash(double price, int num);
}
1.2.2上下文context
/**
 * @author: lpy
 * @Date: 2023/09/14
 */
public class CashContext {
    private Strategy strategy;

    public CashContext(Strategy strategy){
        this.strategy = strategy;
    }

    public double getResult(double price, int num){
        return this.strategy.acceptCash(price, num);
    }
}
1.2.3具体策略算法
/**
 * @author: lpy
 * @Date: 2023/09/14
 */
public class CashReturn1 extends Strategy {
    private double cashCondition;
    private double cashRet;

    public CashReturn1(double cashCondition, double cashRet){
        this.cashCondition = cashCondition;
        this.cashRet = cashRet;
    }

    @Override
    public double acceptCash(double price, int num) {
        if (cashCondition<0){
            throw new RuntimeException("error");
        }

        double result = price * num;
        if (result>cashCondition && (result - cashRet>0)){
            result = result - cashRet;
        }

        return result;
    }
}
/**
 * @author: lpy
 * @Date: 2023/09/14
 * @desc: 具体的算法策略
 */
public class CashRebate1 extends Strategy {
    private double rate;

    public CashRebate1(double rate){
        this.rate = rate;
    }

    @Override
    public double acceptCash(double price, int num) {
        return price * num*rate;
    }
}
/**
 * @author: lpy
 * @Date: 2023/09/14
 * @desc: 具体的算法策略
 */
public class CashNormal1 extends Strategy {
    @Override
    public double acceptCash(double price, int num) {
        return price * num;
    }
}
1.2.4客户端
/**
 * @author: lpy
 * @Date: 2023/09/14
 */
public class StrategyModeTest {
    public static void main(String[] args) {
        CashContext cc = null;

        // 用户输入
        int cashType = 3;
        switch (cashType) {
            case 1:
                cc = new CashContext(new CashNormal1());
                break;
            case 2:
                cc = new CashContext(new CashRebate1(0.7d));
                break;
            case 3:
                cc = new CashContext(new CashReturn1(100d, 10d));
                break;
        }
        double result = cc.getResult(10.5, 10);
        System.out.println(result);
    }
}

1.3痛点

普通的策略模式有一个问题,策略的选择在客户端,客户端必须知道所有的策略。且客户端要知道策略类,耦合度较高。

解决的话,可以把判断的过程进行转移,详见strategyModeWithSimpleFactoryMode,也就是下面的策略模式与简单工厂模式结合,这样的话,客户端就只需要认识一
个类CashContext就可以了。耦合更加降低。

2.策略模式与简单工厂模式结合

2.1结构

2.2代码

具体的策略类、或策略接口是一样的,只需要改造一下context类

2.2context类

context类原来构造函数是传一个策略,现在改成将判断部分由客户端转移到这里。

/**
 * @author: lpy
 * @Date: 2023/09/14
 * @desc: 这里进行改造,将判断的部分由客户端转移,
 */
public class CashContext {
    private Strategy strategy;

    public CashContext(int cashType){
        switch (cashType) {
            case 1:
                this.strategy = new CashNormal2();
                break;
            case 2:
                this.strategy = new CashRebate2(0.7d);
                break;
            case 3:
                this.strategy = new CashReturn2(100d, 10d);
                break;
        }
    }

    public double getResult(double price, int num){
        return this.strategy.acceptCash(price, num);
    }
}

2.2客户端代码
/**
 * @author: lpy
 * @Date: 2023/09/14
 */
public class StrategyModeTest {
    public static void main(String[] args) {
        double result = new CashContext(1).getResult(10.9, 10);
        System.out.println(result);
    }
}

应用

  • 假设有一个电商平台,根据不同的促销活动,计算购物车中商品的总价。

  • 多种支付方式:

    • 银行卡支付
    • 支付宝支付
    • 微信支付
    • 刷脸支付

策略模式优缺点

  • 优点:
    • 可以在运行时动态选择算法,使得系统更加灵活和可扩展。

    • 将算法和客户端代码分离,提高了代码的可维护性和可读性。

    • 支持单一职责原则,每个具体策略类负责一个特定的算法。

  • 缺点:

    • 增加类的数量:策略模式引入了多个具体策略类,每个策略类代表一个不同的算法,这可能会导致类的数量增加。在一些情况下,这可能会使代码变得更加复杂和庞大,增加维护的难度。

    • 客户端必须知道所有策略:客户端代码需要知道所有可用的策略类,并且负责选择合适的策略。这可能导致客户端代码变得复杂,特别是当策略数量较多时。

    • 策略切换开销:在运行时切换策略可能会引入一些开销,因为需要动态创建和切换策略对象。这可能在性能要求严格的应用中成为一个问题。

    • 共享上下文信息困难:不同的策略类通常需要访问相同的上下文信息,但策略模式本身不提供一种简单的方式来共享这些信息。这可能需要额外的管理来确保上下文信息的一致性。

    • 选择恰当的策略:在某些情况下,选择正确的策略可能会变得复杂,因为不同的策略可能受多个因素的影响,这需要客户端代码具有一定的智能来选择策略

7 大设计原则

不能全部遵守 , 也不能不遵守 , 注意平衡 功能 和 系统复杂度 , 找到最合适的一个点 ;

- 单一职责原则(Single Responsibility Principle - SRP):
这个原则规定一个类应该只有一个改变的理由,也就是说,一个类应该只有一个单一的职责。这有助于保持类的简单性和可维护性,因为每个类只需关注一个特定的功能。

- 开放-封闭原则(Open-Closed Principle - OCP):
开放-封闭原则要求系统中的软件实体(类、模块、函数等)应该对扩展开放,对修改封闭。这意味着当需要添加新功能时,应该通过扩展现有代码来实现,而不是修改原有代码。

- 里氏替换原则(Liskov Substitution Principle - LSP):
里氏替换原则规定,子类应该能够替换其父类,而不会影响程序的正确性。也就是说,子类应该继承父类的行为,但可以扩展或修改该行为,但不应该改变父类的行为。

- 接口隔离原则(Interface Segregation Principle - ISP):
接口隔离原则要求一个类不应该强迫其客户端依赖于它们不需要的接口。应该根据客户端的需求定义小而精确的接口,而不是大而笨重的接口。

- 依赖倒置原则(Dependency Inversion Principle - DIP):
依赖倒置原则要求高级模块不应该依赖于低级模块,它们都应该依赖于抽象。具体来说,这意味着应该通过抽象接口或抽象类来定义模块之间的依赖关系,而不是直接依赖于具体实现。

- 迪米特法则(Law of Demeter - LoD):
迪米特法则也被称为最少知识原则,它要求一个对象应该只与其密切的朋友交互,而不应该暴露太多的信息给外部对象。这有助于减少对象之间的耦合度,提高系统的灵活性。

- 合成复用原则(Composite Reuse Principle - CRP):
合成复用原则鼓励通过组合(合成)已有的类来实现新功能,而不是通过继承现有的类。这样可以降低系统的耦合度,并且更加灵活。

源码位置

全部源码github.com/xs-alpha/designPattern

如有内容侵权,麻烦联系博主删除

参考文献

  • 《大话设计模式》

2 条评论

菠萝 · 2023-09-14 15:08

发表评论

Avatar placeholder

您的电子邮箱地址不会被公开。 必填项已用*标注

ICP备案号: 辽ICP备20003309号