设计模式之策略模式

诞生的目的

在传统的软件开发中,当需要为一个问题提供多种算法时,开发者往往采用以下两种方式:

  • 在一个类中提供多个方法,每个方法对应一种算法实现
  • 在一个方法中通过 if…else 或 switch 条件语句进行分支选择

这两种方式都属于硬编码,带来的问题非常明显:

  • 维护困难:增加新算法或修改现有算法,都需要修改原有类的源代码
  • 代码臃肿:大量算法堆积在同一个类中,代码变得复杂且难以阅读
  • 违反开闭原则:对扩展不开放,对修改不封闭——每次增加新算法都要修改已有代码
  • 团队协作冲突:多人同时修改同一个庞大的类,容易产生代码冲突

策略模式正是为了解决这些问题而诞生————将算法从使用它的客户类中抽离出来,独立封装,使算法可以独立变化

核心思想

找出应用中可能变化的部分,把它们独立出来,不要和那些不需要变化的代码混在一起。同时,针对接口编程,而不是针对实现编程

结构

策略模式涉及三个核心角色:

角色 说明
抽象策略(Strategy) 定义公共接口,所有具体策略都必须实现此接口
具体策略(Concrete Strategy) 实现抽象策略接口,提供具体的算法实现
环境类(Context) 持有抽象策略对象的引用,负责调用策略,客户端通过环境类使用策略

UML类图结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
┌─────────────┐          ┌──────────────────┐
│ Context │──────────│ Strategy │
├─────────────┤ ├──────────────────┤
│ - strategy │ │ + algorithm() │
│ + setStrategy()│ └────────┬─────────┘
│ + execute() │ │
└─────────────┘ ┌────────┴────────┐
│ │
┌────────┴────────┐ ┌──────┴──────────┐
│ConcreteStrategyA│ │ConcreteStrategyB│
├─────────────────┤ ├─────────────────┤
│ + algorithm() │ │ + algorithm() │
└─────────────────┘ └─────────────────┘

结构代码范式

Strategy : 定义所有算法的公共接口(AlgorithmInterface)。Context 使用这个接口去调用 ConcreteStrategy 定义的具体算法。

1
2
3
abstract class Strategy {
public abstract void AlgorithmInterface();
}

ConcreteStrategy : 实现 Strategy 中的算法接口(AlgorithmInterface)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class ConcreteStrategyA extends Strategy {
@Override
public void AlgorithmInterface() {
System.out.println("算法A");
}
}

class ConcreteStrategyB extends Strategy {
@Override
public void AlgorithmInterface() {
System.out.println("算法B");
}
}

class ConcreteStrategyC extends Strategy {
@Override
public void AlgorithmInterface() {
System.out.println("算法C");
}
}

Context : 用一个 ConcreteStrategy 来配置。维护一个对 Strategy 对象的引用。

1
2
3
4
5
6
7
8
9
10
class Context {
Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}

public void ContextInterface() {
strategy.AlgorithmInterface();
}
}

客户端

1
2
3
4
5
6
7
8
9
10
11
12
public class StrategyPattern {
public static void main(String[] args) {
Context context1 = new Context(new ConcreteStrategyA());
context1.ContextInterface();

Context context2 = new Context(new ConcreteStrategyB());
context2.ContextInterface();

Context context3 = new Context(new ConcreteStrategyC());
context3.ContextInterface();
}
}

输出

1
2
3
算法A
算法B
算法C

比如可以在Strategy的实现类上面都加上@Component注解,在context中依赖注入的三行代码变成下面这样,就可以自动获取Strategy的实现类为val的map。

1
2
3
4
5
@Autowired
private final Map<String, Strategy> StrategyMap;
public Context(Map<String, Strategy> StrategyMap) {
this.StrategyMap = StrategyMap;
}

这是因为spring可以自动注入同类型 Bean 的集合。它不是某个特殊的配置,而是 Spring 容器的内置行为。可以使用这个来注入map,在策略模式中经常使用。