State模式是一种很常见的模式。尤其在通信领域,很多的协议都有一个状态机来维持协议的运转。而State模式正是实现状态机的绝佳武器。
State模式允许一个对象在其内部状态发生改变时表现不同的行为。举个例子,大家都知道TCP状态机。当TCP连接处于Closed和Established这两种状态时,如果收到一个SYN包,它们的行为是不一样的。处于Closed状态的TCP连接会执行被动打开的行为,而处于Established状态的TCP连接则可能会首先检查Sequence Number,确定是否是一个重复的SYN包,如果不是的话,可能会先关掉原来的连接,然后在执行被动打开的行为。总之,由于TCP连接处于不同的状态,导致了它在同一外部刺激下产生了不同的行为。这正是State模式要解决的问题。
那么,如何实现一个状态机呢?
如果曾经做过C语言下的网络协议开发,大概遇到过用状态迁移表实现的状态机。状态迁移表是一张多维表,一维表示当前状态,一维表示迁移的目标状态,其他的维度表示条件。当受到某种外部刺激时,通过查表的方式确定下一个状态和要执行的动作。但是,这样的一张表是很难维护的,尤其是状态特别多的时候。另一个缺点在于,如果设计不合理,查表的效率可能会很慢。
另一种实现的方式是通过一系列的条件语句。以当前的状态为条件,进入某一个条件分支,然后在条件分支里实现一系列的逻辑和动作。如果状态很少,这是一种既简单又直观的实现方法。但是,同样的,大量的if-else语句是难以维护的,扩充性也不太好。
于是,State模式横空出世。相比于以上两种实现,State模式的特点在于通过继承的方式来扩展状态。这使得状态机具有很好的扩展性。新定义的状态可以很好的融入到既有的框架中来。同时,可以把状态需要执行的动作和状态的切换封装到状态子类之中。每个状态只需要了解自己需要做什么和下一个状态是什么,这样降低了开发的难度。另一个很重要的好处在于,开发者可以把上下文数据和状态完全分离。这样,状态本身就完全变成了对算法的封装,而上下文可以不断的重入状态。因此,只需要一个状态的实例即可,类似于Flyweight模式。
说了这么多,到时候看看State模式的结构图了。State模式的结构图如下:
如上图所示,状态模式的主要参与者是Context类和State类及其子类。在这个结构中,作为客户只需要了解Context类和它的接口。至于都存在哪些状态,状态之间如何切换对于客户而言都是透明的。对于Context类,它始终维持着当前的状态和与状态有关的数据,并把来自客户的请求转发给State。而State基类只是一个接口,它告诉了Context自己能够做什么。比如在上图中,State只有一个接口,就是goNext。它的继承类StateOne,StateTwo和StateThree则是State的具体实现。同时它们之间存在着某种关联,根据Context的内容可能从一个状态迁移到另一个状态。
从上图中,我们也能够看到状态模式实现上的一些特点:
1. Context会把用户的请求转发给State.goNext,并把自身作为一个参数传过去。这是因为State需要利用Context的信息来决定做什么,如何迁移。
2. State可以在goNext中实现各种逻辑,完成各种动作,并且调用context.setState完成状态的切换。
3. 每个State都可以独立开发,并且只需要一个实体即可。
总结:
State模式是一个简单但是强大的行为模式。通过状态和上下文的分离实现了数据和算法的分离,把Context的复杂性分散到各个State的子类中。通过继承,使得状态机具有良好的可扩展性。是实现状态机的绝佳选择。