策略模式

2 阅读3分钟

策略模式(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小时。

🎯 策略模式的核心组成

从上面的例子可以看出,策略模式主要由三部分组成:

  1. 上下文 (Context):例子中的 Traveler。它负责持有一个具体策略的引用,并对外提供一个执行策略的接口 (startTravel)。上下文本身不关心策略的具体实现。
  2. 策略接口 (Strategy Interface):例子中每个策略类都实现的 travel 方法。它定义了所有具体策略必须遵守的共同接口。
  3. 具体策略 (Concrete Strategies):例子中的 AirplaneStrategyTrainStrategyCarStrategy。它们实现了策略接口,封装了具体的算法或行为。

✅ 优点

  • 开闭原则:可以轻松添加新的策略,而无需修改现有的上下文或策略类。
  • 避免多重条件语句:不再需要使用庞大的 if-elseswitch-case 来选择行为,代码更清晰。
  • 提高复用性:策略类可以被不同的上下文复用。
  • 分离关注点:将算法的定义、使用和执行分离开来,使代码更易于理解和维护。

❌ 缺点

  • 类数量增加:每个策略都需要一个单独的类,可能会导致系统类数量膨胀。
  • 客户端必须了解策略:客户端需要知道有哪些策略可选,并自行决定使用哪一个。
  • 策略间无法直接通信:如果多个策略有共享的逻辑或数据,处理起来会比较麻烦。

💎 总结一下

策略模式的核心就是将变化的部分(算法)封装起来,让它们可以独立地变化和替换,而不影响使用它们的代码

它非常适合用在以下场景:

  • 一个系统需要动态地在几种算法中选择一种。
  • 系统中存在大量的条件判断,这些判断仅用于选择不同的行为。
  • 需要将算法的定义和使用分离开来。