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