LOADING

加载过慢请开启缓存 浏览器默认开启

设计模式-Strategy策略模式

2023/7/7 java DesignPattern
  1. Strategy策略模式
    1. why
    2. what
    3. who 参与者
    4. structure 策略模式类图
      1. 策略模式实例
    5. how 实例
    6. where JDK中使用策略模式的例子
    7. when 策略模式使用场景
    8. Pros and cons 优缺点
      1. 优点
      2. 缺点
  2. summary

Strategy策略模式

why

当我们需要对一个正文(输入)流进行处理,需要使用许多不同的算法。
这个时候,将这些算法硬编码进使用它们的类当中是不可取的,
原因如下:

  • 客户程序异常庞大同时难以维护
  • 不同的时候需要不同的算法,我们不想支持我们并不使用的算法
  • 当其中某些算法是客户程序难以分割的算法时,增加/改变现有的算法将十分困难复杂
    对于掘金社区的支付业务例子,在没有使用设计模式时,一般使用if…else条件语句的方式
    虽然能支持现有的业务需求,但是当业务需求发生改变时,比如增加新的支付方式,或者将某一个支付方式下线,则需要对PaymentService进行修改,显然这种设计不符合开闭原则[1](对修改关闭,对扩展开放)

what

策略模式(Strategy Pattern)是一种行为型设计模式,它定义了一系列算法,把它们一个个封装起来,并且使它们可以相互替换。
策略模式让算法独立于使用它的客户而变化。

who 参与者

策略模式涉及到三个角色:

  • Context(环境类):维护一个策略类的引用,负责与具体的策略类交互。
  • Strategy(抽象策略类):定义所有支持的算法的公共接口。通常是一个接口或者抽象类,所有具体策略类需要实现这个公共接口。
  • ConcreteStrategy(具体策略类):实现具体的算法。

structure 策略模式类图

策略模式

策略模式实例

策略模式实例
这是掘金社区的一个支付策略实现实例(信用卡 微信 支付宝支付)

how 实例

一个简单的策略模式示例,演示如何使用策略模式定义不同的排序算法,并在不同的场景下使用。
1.定义策略接口(Strategy):

public interface SortingStrategy {
    void sort(int[] numbers);
}

2.实现各个具体策略(ConcreteStrategy):

public class BubbleSortStrategy implements SortingStrategy {
    @Override
    public void sort(int[] numbers) {
        // 实现冒泡排序算法
    }
}

public class QuickSortStrategy implements SortingStrategy {
    @Override
    public void sort(int[] numbers) {
        // 实现快速排序算法
    }
}

3.定义环境类(Context):(业务上就是Service)

public class SortingContext {

    public void sort(SortingStrategy strategy , int[] numbers) {
        
        strategy.sort(numbers);  
    }
}

4.使用策略模式:

public class Main {
    public static void main(String[] args) {
        SortingContext context = new SortingContext();
        int[] numbers = {3, 1, 4, 1, 5, 9};

        context.sort(new BubbleSortStrategy() , numbers);
        context.sort(new QuickSortStrategy() , numbers);
    }
}

where JDK中使用策略模式的例子

在JDK中最经典的使用策略模式的例子就是Collections.sort(List<T> list, Comparator<? super T> c)方法,
这个方法接受一个比较器Compartor参数,客户端在运行时可以传入一个比较器的实现,
sort()方法中根据不同实现,按照不同的方式进行排序。

when 策略模式使用场景

多个支付方式,缓存策略,排序算法,不同的第三方对接等等。

Pros and cons 优缺点

优点

  • 提高了程序的灵活性。可以在运行时动态替换算法。
  • 符合开闭原则,新增算法无需修改现有代码。
  • 将具体的策略与调用者分离,易于维护和测试。策略模式也遵循单一职责原则。
  • 提高代码的复用性。将算法的定义与其具体实现进行解耦,有助析取算法公共功能。

缺点

  • 客户端需要知道所有的策略类,并根据具体场景选择合适策略,增加了客户端的复杂度。
  • 对于较少变化的策略,反而会增加不必要的通信开销。因此可能某些简单的ConcreteStrategy可能不使用接口传递给他们的信息。
  • 如果策略类较多,会导致类的数量增多,增加系统的复杂度。并且Strategy增加了一个应用中对象的数目。
    针对这一情形,可以将Strategy实现为可供各个Context共享的无状态对象来减少这一个开销[2]

summary

  • 代码基于接口而非基于实现编程;
  • 为程序提供新的扩展点,定义一类算法类,需要扩展时,只需要增加对应策略的算法实现即可;
  • 控制代码复杂度,添加新策略时,最小化、集中化代码改动,减少引入 bug 的风险。
  • 消除大量的if-else和switch代码情形;
  • 策略模式能够起到解耦的作用,主要是三个方面 解耦策略的定义、创建、和使用。

下面是注脚的解释

[1]面向对象的六大原则↩(返回原文)

  • 单一功能原则 一个类只做一件事情
  • 开闭原则 一个软件实体类,模块和函数应该对扩展开放,对修改关闭
  • 里氏替换原则 子类可以扩展父类的功能,但不能改变父类原有的功能
  • 依赖倒置原则 抽象不应该依赖细节,细节应该依赖于抽象,换一句话说,就是要针对接口编程,不要对实现编程
  • 接口分离原则 系统解开耦合,从而容易重构,更改和重新部署
  • 迪米特原则 一个类应该对自己需要耦合或者调用的类知道得最少,这有点类似于接口隔离原则中的最小接口的概念

[2]将策略设计成无状态对象↩(返回原文)

将策略接口定义为无状态的接口,即不包含任何成员变量:

public interface Strategy {
  void execute();
}

实现具体的策略类时,确保策略类没有任何状态相关的成员变量,只关注于执行某项操作。

public class ConcreteStrategyA implements Strategy {
    @Override
    public void execute() {
        // 执行具体策略A的操作
    }
}

将策略对象的创建和管理放到上下文中(Context):

public class Context {
    private Strategy strategy;

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

    public void executeStrategy() {
        strategy.execute();
    }
}

在使用时,可以共享同一个策略对象给多个上下文,因为策略对象是无状态的。创建多个上下文并使用相同的策略对象执行操作:

Strategy sharedStrategy = new ConcreteStrategyA();  // 创建共享的策略对象

Context context1 = new Context(sharedStrategy);
context1.executeStrategy();

Context context2 = new Context(sharedStrategy);
context2.executeStrategy();

这种设计方法可以有效降低内存使用和对象创建的开销。但需要注意的是,如果策略需要维护状态,或者具有可变的成员变量,那么将其设计为无状态对象就不适用了。↩(返回原文)