JavaScript设计模式:策略模式

260 阅读5分钟

4.gif

软件开发中,使用硬编码(Hard Coding)实现将导致系统违背开闭原则,扩展性较差,且维护困难,这时就可以定义一些独立的类来封装不同的算法,每一个类封装一种具体的算法:->策略类 ->策略模式

模式概念

策略模式(Strategy Pattern) 是一种对象行为模式,它定义了一系列算法,并将每一个算法封装起来,使它们可以互换。策略模式让算法的变化独立于使用算法的客户。

策略模式允许在运行时选择算法的行为,而不是在编译时静态绑定。这种模式非常适合用于需要动态选择算法的场景。

模式结构

Snipaste_2024-07-23_11-16-42.jpg

  • Context(环境类) :持有一个策略对象的引用,最终给客户端使用。
  • Strategy(策略接口) :定义所有支持的算法的公共接口。
  • ConcreteStrategy(具体策略类) :实现策略接口,提供具体的算法实现。

每一个封装算法的类称之为策略类,策略模式提供了一种可插入式算法的实现方案。

代码实现

// 策略接口
class Strategy {
  execute() {
    throw new Error("execute method must be implemented");
  }
}

// 具体策略类A
class ConcreteStrategyA extends Strategy {
  execute() {
    console.log("Executing ConcreteStrategyA");
  }
}

// 具体策略类B
class ConcreteStrategyB extends Strategy {
  execute() {
    console.log("Executing ConcreteStrategyB");
  }
}

// 环境类
class Context {
  constructor(strategy) {
    this.strategy = strategy;
  }
  //设置策略
  setStrategy(strategy) {
    this.strategy = strategy;
  }
  //执行策略算法
  executeStrategy() {
    this.strategy.execute();
  }
}

// 使用示例
const context = new Context(new ConcreteStrategyA());
context.executeStrategy(); // 输出: Executing ConcreteStrategyA

context.setStrategy(new ConcreteStrategyB());
context.executeStrategy(); // 输出: Executing ConcreteStrategyB

在这段代码中:

  • Strategy 是策略接口,定义了所有策略必须实现的 execute 方法。
  • ConcreteStrategyAConcreteStrategyB 是具体策略类,实现了 Strategy 接口。
  • Context 是环境类,持有一个策略对象的引用,并提供 executeStrategy 方法来执行策略。

通过在运行时切换 context 的策略对象,可以改变执行的算法。

模式效果

  • 优点

    • 算法的封装:每个算法封装在独立的类中,符合单一职责原则,易于管理。
    • 易于扩展:新增算法只需新增一个具体策略类,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活的增加新的算法或行为,符合开闭原则。
    • 易于切换:可以在运行时自由切换算法,增加灵活性。
    • 解耦算法和使用算法的类:算法的实现和使用算法的类解耦,算法相似情况下,避免了多重条件选择语句。
  • 缺点

    • 客户端必须知道所有的策略类:客户端需要了解所有策略类的具体实现,以便在运行时做出选择。
    • 增加系统复杂度:可能会增加系统中类的个数。
    • 无法同时使用多个:客户端无法同时使用多个策略类,一次只能使用一个策略。

模式应用

策略模式属于比较简单的设计模式,将不同的策略独立封装,可以轻松的在使用时切换策略,比如以下几种场景中可以使用:

  1. 动画的效果选择

先将不同的动画效果封装到独立的类中,需要时根据不同的条件应用不同的动画效果。

  1. 数组的排序算法

将不同的排序算法如快速排序、冒泡排序、选择排序等封装到独立的类中,处理数据时,根据不同的标准使用不同的排序算法。

  1. 支付业务

电子商务应用中,可能需要支持多种支付方式,每种支付方式都是一个策略。

典型策略模式和非典型策略模式

典型策略模式实现,策略通常是由实现了共同接口的组成的,而在我们平时开发过程中,策略直接由对象的属性表示,这是 JavaScript语言特性支持的一种简化实现方式,可能并不会封装较多的策略类。

非典型策略模式指的是不严格遵循策略模式原始定义,但在实现上仍然利用了策略模式核心思想的设计方式。这种灵活性使得策略模式在 JavaScript 中特别有用,尤其是在处理需要高度动态行为的应用程序时。

使用映射来代替 if-else 语句是一种常见的模式,特别是在有多个条件分支时。以下是如何使用映射来实现支付策略的例子:

const paymentStrategies = {
  //会员
  1: money => {
    // ... 一些别的逻辑
    return money * 0.8;
  },
  //优惠卷
  2: money => {
    // ... 一些别的逻辑
    return money - 5;
  },
  //普通
  3: money => {
    // ... 一些别的逻辑
    return money;
  },
};

function payUsingMapping(payType, money) {
  const strategy = paymentStrategies[payType];
  if (!strategy) {
    throw new Error(`Payment strategy for type ${payType} is not defined.`);
  }
  return strategy(money);
}

console.log(payUsingMapping(1, 362));
console.log(payUsingMapping(3, 564));

这种代码方式使用策略模式的思想,将策略的实现与使用策略的代码解耦,支付时只需要调用payUsingMapping方法传入支付方式和金钱即可,无需考虑具体的支付流程,修改策略或添加新策略也与payUsingMapping无关。

如果你学过策略模式,在写代码时就可以写出更加优雅的代码,可以减少 if-else 语句的使用频率,降低代码耦合度,代码的可扩展性也会提高。

❤今天的分享就到这里,希望可以帮助到你!假如你对文章感兴趣,可以来我的公众号:小新学研社。

13.gif