设计模式:策略模式/状态模式

设计模式是通用的、可复用的代码设计方案,也可以说是针对某类问题的解决方案,因此,掌握好设计模式,可以帮助我们编写更健壮的代码。

wiki中将设计模式分为四类,分别是:

  • 创建模式(creational patterns)
  • 结构模式(structural patterns)
  • 行为模式(behavioral patterns)
  • 并发模式(concurrency patterns)

策略模式和状态模式属于其中的行为模式,行为模式------从名称上就可以看出------与动作、操作有关。

这两种模式我接触下来,感觉存在一定的相似性。状态模式中通常会存在一个内部状态,状态改变时行为也会发生改变,而策略模式是针对不同条件下的行为进行封装。总的来说,两者都是在不同条件下有不同的行为。接下来我们分别来看一下。

策略模式

首先看策略模式,根据针对它的概述,貌似就是一系列算法的封装

Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

当然策略模式不止关于算法的定义,还有对算法的调用。关于这一点我们在策略模式对应的wiki:Strategy pattern页面也能看到相应的描述。

Instead of implementing a single algorithm directly, code receives run-time instructions as to which in a family of algorithms to use.

这句话的大概意思是:代码在运行时接收指令,决定使用一系列算法中的哪一种。在这里一种算法就是对应一个策略。

这个描述很容易让人联想到代码中常见的条件语句if-elseif-else,在条件分支根据不同的指令执行不同的操作。但是很显然,既然是一系列的算法,那就说明可能会有很多、甚至是大量的条件,那么可想而知,如果我们直接使用if-else语句来编写执行代码的话,这部分代码会非常长,并且这会破坏软件设计原则中的单一功能原则,这段代码除了判断条件,还要根据不同的条件执行不同的细节操作。

策略模式中的"策略",其实指的就是算法。然后条件判断作为一个入口,去调用对应的"策略"。所以我们看到wiki中有下面这段描述:

Typically, the strategy pattern stores a reference to some code in a data structure and retrieves it. This can be achieved by mechanisms such as the native function pointer, the first-class function, classes or class instances in object-oriented programming languages, or accessing the language implementation's internal storage of code via reflection.

这段话的意思是:策略模式会在数据结构中存储对某些代码的引用,并对其进行检索。这可以通过本地函数指针、一级函数、面向对象编程语言中的类或类实例,或通过反射 访问 语言实现的代码内部存储等机制来实现。

简单来理解,就是把一系列相关操作封装成函数,一个函数就对应一个算法的实现。

策略模式与开闭原则

我们知道在软件设计原则中有一条是:对扩展开放,对修改封闭。策略模式与开闭原则是一致的。

According to the strategy pattern, the behaviors of a class should not be inherited. Instead, they should be encapsulated using interfaces.

根据策略模式,类的行为不应被继承,它们应使用接口进行封装。也就是说,我们最好不要对父类本身做修改,而是使用接口对子类的行为进行扩展。在wiki中使用了Java来举例子,在JavaScript中也可以做类似的处理,比如不把对应的策略函数加在对象自身,而是统一放在一个地方进行调用,也就是上面所说的**在数据结构中存储对某些代码的引用**。比如下面这个例子:

某商场中的商品在不同阶段的价格满足固定的逻辑,做了以下封装:

javascript 复制代码
const priceProcessor = {
  pre(originPrice) {
    if (originPrice >= 100) {
      return originPrice - 20;
    }
    return originPrice * 0.9;
  },
  onSale(originPrice) {
    if(originPrice >= 100) {
      return originPrice - 30;
    }
    return originPrice * 0.8;
  },
  back(originPrice) {
    if(originPrice >= 200) {
      return originPrice - 50;
    }
    return originPrice;
  },
  fresh(originPrice) {
    return originPrice * 0.5;
  },
};

pre、onSale、back、fresh分别代表了在预热、大促、返场、尝鲜四种阶段下的价格处理。

在上述代码中,我们在priceProcessor这个数据结构中存储了针对不同阶段下对价格的处理逻辑,也就是各种封装的函数。我们可以调用这些引用,从而实现在不同条件下执行不同的算法。

这样,当我们策划新的促销活动时,只需要在priceProcessor这个结构中增加新的处理逻辑,而不需要影响商品对象和其他的处理逻辑,并且这样子处理后,测试流程中就只需要测试新的处理逻辑,而不需要回归测试整体功能。

每个处理逻辑有单独的函数实现,这也方便不同条件下的算法替换,比如在某次商场大促,想要使用返场的价格,就可以直接调用priceProcessor.back方法,而不需要编写重复冗余的代码。

状态模式

策略模式的核心很简单,就是单一功能的函数封装。接下来我们继续看状态模式。

The state pattern is a behavioral software design pattern that allows an object to alter its behavior when its internal state changes.

状态模式也很简单,就是允许对象在内部状态发生变化时改变其行为。

状态模式的"状态",就是指对象内部的状态,也就是说,这个模式针对的是存在内部状态的对象。

The state pattern can be interpreted as a strategy pattern, which is able to switch a strategy through invocations of methods defined in the pattern's interface.

我们可以看到wiki这部分也有说,状态模式可以解释为一种策略模式。其实上也就是说,根据不同的状态切换策略;在策略模式下,是根据不同的条件切换不同策略,这个是广泛意义下的条件,而状态模式中,不同条件就特定为不同的内部状态。

这样处理后,就不需要使用条件语句了,可以直接通过不同状态映射不同的行为。

在状态模式的wiki页面中,也列举了它所解决的主要问题:

The state pattern is set to solve two main problems:[4]

  • An object should change its behavior when its internal state changes.
  • State-specific behavior should be defined independently. That is, adding new states should not affect the behavior of existing states.

在某类场景中,第一,对象应根据其内部状态的改变来改变其行为。

第二,特定于状态的行为应独立定义。也就是说,添加新状态不应影响现有状态的行为。

这里看第二点,其实和策略模式的场景很类似。

对应这两个问题,状态模式描述了以下解决方案:

In this, the pattern describes two solutions:

  • Define separate (state) objects that encapsulate state-specific behavior for each state. That is, define an interface (state) for performing state-specific behavior, and define classes that implement the interface for each state.
  • A class delegates state-specific behavior to its current state object instead of implementing state-specific behavior directly.

第一,是定义独立的状态对象,为每个状态封装特定于状态的行为。

第二,类将特定于状态的行为委托给其当前的状态对象,而不是直接实现特定于状态的行为。

因此,状态模式中的关键就在于对状态对象的实现。比如下面这个例子:

一个养生壶有不同的功能,当切换不同的功能时我们可以认为它处于不同的工作状态。

javascript 复制代码
class HealthPot {
  constructor() {
    this.state = new State();
  }

  changeState(status) {
    this.state.status = status;
    // 若状态不存在,则返回
    if(!this.state.statusToProcessor[status]) {
      return;
    }
    this.state.statusToProcessor[status]();
   }
}

class State {
  constructor() {
    this.status = '';
  }
  statusToProcessor = {
    water() {
        console.log('煮开水');
    },
    flowersTea() {
        console.log('煮花草茶');
    },
    fruitsTea() {
        console.log('煮水果茶');
    },
    keepWarm() {
        console.log('保温');
    }
  }
}

const hp = new HealthPot();
hp.changeState('flowersTea');

在上述代码中,如何实现特定状态的行为与养生壶本身无关,只与状态对象有关。养生壶的行为只是改变状态,并调用对应方法,这样如果后续有新的状态增加,也不用去修改养生壶这个具体的对象,相当于一种拆分行为。

这其实就有点类似于vue中的状态管理工具vuex。

总结

策略模式和状态模式两者存在一定的相似性,但是策略模式封装的函数其独立性会更高,而状态模式中封装的函数依赖于主体的状态,具体操作代码也可能依赖主体的其他属性,比如养生壶例子中,执行各种功能时,需要保证壶中有水,并且要判断是否通电中等等。

简单来说就是一种拆分、封装的行为,满足软件设计原则中的单一职责和开闭原则。

一般在应用开发初期,由于功能简单,开发者可能不会特别在意拆分,并且通常而言不太提倡提前优化,所以会在之后的维护和迭代中,应用这些模式来优化和重构代码;但是有时设计良好的代码,会更便于代码的维护。

相关推荐
李广坤7 小时前
状态模式(State Pattern)
设计模式
李广坤8 小时前
观察者模式(Observer Pattern)
设计模式
李广坤9 小时前
中介者模式(Mediator Pattern)
设计模式
李广坤9 小时前
迭代器模式(Iterator Pattern)
设计模式
李广坤9 小时前
解释器模式(Interpreter Pattern)
设计模式
阿无,12 小时前
java23种设计模式之前言
设计模式
程序员鱼皮12 小时前
让老弟做个数据同步,结果踩了 7 个大坑!
java·后端·计算机·程序员·编程·职场
Asort13 小时前
JavaScript设计模式(八):组合模式(Composite)——构建灵活可扩展的树形对象结构
前端·javascript·设计模式
数据智能老司机13 小时前
数据工程设计模式——数据基础
大数据·设计模式·架构
笨手笨脚の16 小时前
设计模式-代理模式
设计模式·代理模式·aop·动态代理·结构型设计模式