我在另一篇博客里声称:做技术的思路是优先“怎么用”、而后再“是什么”。然而这里,我却想讨论一下状态模式与策略模式“是什么”,以及它们之间的区别。
      这并不是打脸,而是我在经过长久的思考“怎么用”之后,终于明白了它们“是什么”。

状态机

      让我想明白的契机,是“如何实现状态机(当然,是有限确定状态机)”这样一个问题。下图是一个简单的状态机:
      状态机

策略与状态

      我原本计划用这样一套类来实现这个状态机:
      策略模式类图
      画好类图之后我发现,这是个典型的策略模式。但是从直觉上来看,状态模式才是状态机的天然盟友。因此我又尝试着画了一套状态模式的图:
      状态模式类图

对比

      一眼看上起,这两张类图一模一样。当然,仔细看看,一定能发现其中的区别。相同点会带来相同的优点,不同之处造就了各自的缺点。

相同点

      这两张类图似乎一般无二:一个对外接口;若干个接口方法;一个基类;若干个子类;另外有一个工厂与之配合。
      之所以有如此高的相似度,是因为这两种模式的基本思路都是一样的:对外提供一个抽象,对内扩展为不同的实现。

不同点

      看起来,只有接口方法不同:策略模式用一个接口方法来处理所有的状态迁移;而状态模式则用不同方法来处理迁移到不同状态时的动作。
      这个看起来不起眼的区别,实际上代表了这两种模式截然不同的“抽象”方式。策略模式将状态迁移的“操作”设计为抽象接口;而状态模式的抽象接口代表了“状态”本身。换句话说,策略模式是对行为的抽象,状态模式是对数据的抽象。

优点

      面向接口的设计和编程方式可以带来数不尽的有点:接口对内高内聚,对外低耦合;实现类内部高内聚,彼此之间低耦合;对新增实现类保持开放,对修改现有类尽量封闭;等等等等。

缺点

      用策略模式实现状态机,从代码还原到状态机时会遇到一些困难。在我实践过的代码中,团队成员都反应过这一点。用状态模式来做会更明白一些:因为状态模式同时封装了数据和行为,虽然也只是一个“片段”,但片段中包含的信息更多。
      用状态模式实现,则会遇到实例化的问题。这是由于包含有数据,所以“状态”实例线程不安全。因而每次执行状态迁移操作时,系统都需要使用一个新的实例。当然,这个问题可以变通解决,但是相比天然只有行为、通常只需要单例实现的策略模式,这还是算得上一个缺点的。