跳到主要内容

策略模式

行为型 behavioral 允许算法独立性地随使用者改变

设想这种场景:一份数据可能会做不同的处理,或者系统需要动态地在多个算法中选择一个

这种情况下,数据一般是相对不易改变的部分,这里指的是数据的结构,而非具体的数值。对数据的处理逻辑可能会经常改变。

如果使用硬编码实现逻辑,需要使用大量条件选择语句

会破坏系统的开闭原则 Open/Closed Principle,导致低扩展性和维护困难

另外还可以降低用户的心智负担,用户不用了解复杂的算法数据结构 同时增加了算法的安全性

还是那句话:在不常变和常变之间做出区分,引入中间抽象把它们隔离开。

把数据的处理提取成接口,剥离处理方法的具体实现,这种方式被成为策略模式。

UML

  • Context: Context class 上下文类
  • Stragegy: Abstract strategy class 抽象策略类
  • ConcreteStrategy: Concrete strategy

实际案例

排序

最简单常见的策略模式如下

const nums = [4, 3, 0, 8, 2];

// 从小到大
nums.sort(function (a, b) {
return a - b;
});

// 从大到小
nums.sort(function (a, b) {
return b - a;
});

nums 数组提供了一个 sort 方法,具体怎么排序则由调用方自己决定,只要排序函数满足接口即可。

这样 nums 就不用提供 sortMinToMax/sortMaxToMin 等排序方法,调用方爱怎么排序就怎么排序,自己来。

消息处理方案

策略模式还有一个很经典的应用场景:消息通信中,对不同的消息做不同的处理。

class Message {
public code;
public bytedata;
}

对消息的处理可以抽象成共同的接口:

class MsgHandler {
// 消息状态码
public codes;
public handle(msg);
}

// 消息处理的实现
class MsgHandler404 extends MsgHandler {
MsgHandler404() {
super();
this.codes = {
404,
};
}
public handle(msg) {
// ... doSomething
}
}

// 类似发布订阅,但是固定msg订阅固定事件
class Handlers {
private handlers = new Map();
// register 提供消息处理器的插拔机制
public void register(handle:MsgHandler) {
for (let i = 0; i < handler.codes.length; i++) {
code = handler.codes[i];
list = handlers.get(code);
if (list == null) {
list = [];
handlers.put(code, list);
}
list.push(handler);
}
}
// 不再使用 switch (msg.code) 的方式处理消息
public handle(msg) {
list = handlers.get(msg.code);
for (let i = 0; i < list.length; i++) {
handler = list.get(i);
// 每个handler处理一下
handler.handle(msg);
}
}
}

优点 Pros

  • 完美支持开闭原则
  • 提供管理算法相关家族的方法 manage realated families of algorithms
  • 提供继承关系的替代 an alternative to inheritance relationships
  • 避免多个条件选择语句(硬编码)avoid multiple condition selection statements
  • 提供复用算法的策略 a mechanism for reuse of algorithms; 允许不同的上下文类条件性复用策略类

弊端

  • 用户必须意识到所有策略类,并决定使用哪个
  • 会导致系统中大量实例类的创建
  • 无法同时使用复数个策略类, use multiple strategy classes simultaneously in a client