策略模式

问题

比如要压缩一张图片,可选择的压缩算法有 jpgpnggif 等等。我们实现了一个 ImageCompressor class,提供了 compress 方法来处理这个任务,代码如下。

class ImageCompressor {
  constructor() {}
  compress(img, algorithm) {
    let compressedImg = null
    switch (algorithm) {
      case 'jpg':
        // jpg 的压缩算法
        // compressedImg = ...
        break
      case 'png':
        // png 的压缩算法
        break
      case 'gif':
        // gif 的压缩算法
        break
      default:
        break
    }
    return compressedImg
  }
}

const imageCompressor = new ImageCompressor()
const pngImg = imageCompressor('exampleImageFile', 'png')
const jpgImg = imageCompressor('exampleImageFile', 'jpg')

可以看到在上面的代码中,所有压缩算法都是在 compress 方法中实现的,而在实际中,每种算法的逻辑都十分复杂,如果都写在同一个方法中,这个方法很快就会膨胀了。这种写法导致的问题就是,如果之后要修改或者新增某个算法,都很有可能会不小心影响到其他代码。

为了代码更容易维护,我们需要把这些算法拆分成独立的小块,并通过一个“代言人”来“管理”它们。

用“代言人”和“管理”好像不那么准确,但我又想不到什么词了,意会意会。

什么时候使用策略模式

从以上代码中可以观察到,compress 的输入和输出是差不多的:

  • 输入一张图片

  • 输出一张压缩后的图片

而不同的是:

  • 压缩处理的方法

遇到这种模式的问题我们都可以使用策略模式来解决:

  • 把不同的 处理方法 抽离成各自独立的 class

  • 每一个 处理方法 就是一个解决问题的 策略

  • 再通过一个 Context class 来提供统一的对外接口,Context 内部再调用不同的 策略 方法

一个简单例子

假设要写一个可以同时处理两数加减乘除的函数,我们可能会实现成以下的样子:

doMath 函数接收两个操作数 ab,以及一个算术类型 operation 作为参数,然后根据不同的算术类型返回不同的数学计算结果。

可以观察到,多次调用 doMath 函数的共同点在于:

  • 都是输入两个数字

  • 得到这两个数字经过某种计算后的结果作为返回值

而不同点就在于:

  • 具体的计算方式是不一样的

这个问题模式就很适合使用策略模式来解决。

套用策略模式

用 OOP 的形式来实现的话,我们先把上面的例子改写成 class 的形式吧,改写方式之一:

目前所有算法都是在 SimpleMath 中实现的,接下来我们尝试把每个算法抽离成独立的 class:

策略模式的构成

问题

  • 策略模式中有几个角色?

  • 每个角色分别负责什么?

  • 各个角色之间是如何联系的?

回答

策略模式中有 3 个角色:

  • Client: 调用方,是有某项任务需要完成的角色,可以有很多个;

  • Context: 比如上方的 SimpleMath,是负责完成某项任务的角色,但只知道需要完成一项任务,并不知道任务具体应该如何完成,只有一个;

  • Strategy: 完成任务的具体方法,可以有很多个;

Client 通过调用 Context 并指定某个 Strategy 来间接调用相应 Strategy 的某个方法。

其实不用 class 来实现,只要是符合这种思想的都是策略模式吧,把 strategy 拆分成函数也行吧,个人看法个人看法。

Last updated

Was this helpful?