策略模式(Strategy Pattern) 是一种行为设计模式,它的核心思想是:定义一系列算法,将每个算法封装起来,并使它们可以相互替换。这样算法的使用和算法的实现就被分离开来,互不影响。
我们可以通过一个生活中的例子来理解它:
🌰 生活例子:出行旅游
你要从上海去北京,有几种出行策略:
- 坐飞机
- 坐高铁
- 自驾
无论你选择哪种方式,最终目的都是“到达北京”。这些不同的出行方式就是不同的策略。你可以在出发前,根据时间、预算、喜好等因素,动态地选择最合适的策略,而不需要改变“去北京”这个动作本身。
📝 代码示例(JavaScript)
我们把这个例子用代码实现出来,结构会更清晰。
// 1. 定义具体的策略类(算法族)
class AirplaneStrategy {
travel() {
console.log('乘坐飞机前往北京,耗时约2小时。');
}
}
class TrainStrategy {
travel() {
console.log('乘坐高铁前往北京,耗时约5小时。');
}
}
class CarStrategy {
travel() {
console.log('自驾前往北京,耗时约12小时。');
}
}
// 2. 定义上下文类,它需要使用策略
class Traveler {
// 设置当前使用的策略
setStrategy(strategy) {
this.strategy = strategy;
}
// 执行旅行动作,具体行为委托给策略对象
startTravel() {
if (!this.strategy) {
console.log('请先选择出行方式。');
return;
}
this.strategy.travel();
}
}
// 3. 客户端代码:根据情况动态选择策略
const traveler = new Traveler();
// 用户想快点到,选择飞机策略
traveler.setStrategy(new AirplaneStrategy());
traveler.startTravel(); // 输出:乘坐飞机前往北京,耗时约2小时。
// 用户预算有限,改选火车策略
traveler.setStrategy(new TrainStrategy());
traveler.startTravel(); // 输出:乘坐高铁前往北京,耗时约5小时。
🎯 策略模式的核心组成
从上面的例子可以看出,策略模式主要由三部分组成:
- 上下文 (Context):例子中的
Traveler。它负责持有一个具体策略的引用,并对外提供一个执行策略的接口 (startTravel)。上下文本身不关心策略的具体实现。 - 策略接口 (Strategy Interface):例子中每个策略类都实现的
travel方法。它定义了所有具体策略必须遵守的共同接口。 - 具体策略 (Concrete Strategies):例子中的
AirplaneStrategy、TrainStrategy、CarStrategy。它们实现了策略接口,封装了具体的算法或行为。
✅ 优点
- 开闭原则:可以轻松添加新的策略,而无需修改现有的上下文或策略类。
- 避免多重条件语句:不再需要使用庞大的
if-else或switch-case来选择行为,代码更清晰。 - 提高复用性:策略类可以被不同的上下文复用。
- 分离关注点:将算法的定义、使用和执行分离开来,使代码更易于理解和维护。
❌ 缺点
- 类数量增加:每个策略都需要一个单独的类,可能会导致系统类数量膨胀。
- 客户端必须了解策略:客户端需要知道有哪些策略可选,并自行决定使用哪一个。
- 策略间无法直接通信:如果多个策略有共享的逻辑或数据,处理起来会比较麻烦。
💎 总结一下
策略模式的核心就是将变化的部分(算法)封装起来,让它们可以独立地变化和替换,而不影响使用它们的代码。
它非常适合用在以下场景:
- 一个系统需要动态地在几种算法中选择一种。
- 系统中存在大量的条件判断,这些判断仅用于选择不同的行为。
- 需要将算法的定义和使用分离开来。