第2章 状态模式

2.1 从生活中领悟状态模式

2.1.1 故事剧情—人有少、壮、老,水之固、液、气

一个天气晴朗的周末,Tony 想去图书馆给自己充充电。于是背了一个双肩包,坐了一个多小时地铁,来到了首都图书馆。走进一个阅览室,Tony 看到一个青涩的小女孩拿着一本中学物理教科书,认真地看着热力学原理……女孩的容貌像极了 Tony 中学的物理老师,不知不觉Tony想起了他那可爱的老师,想起了那最难忘的一节课……

Viya老师站在一个三尺讲台上,拿着一本教科书,给大家讲着水的特性。人有少年、壮年、老年三个不同的阶段;少年活泼可爱,壮年活力四射,老年充满智慧。水也一样,水有三种不同的状态:固态—冰,坚硬寒冷,液态—水,清澈温暖,气态—水蒸气,虚无缥缈。更有意思的是水不仅有三种状态,而且三种状态还可以相互转换。冰吸收热量可以融化成水,水吸收热量可以汽化为水蒸气,水蒸气释放热量可以凝固成冰……

虽然时隔近十年,但Viya老师那优雅的容貌和生动的课堂依然历历在目!

2.1.2 用程序来模拟生活

水是世界上最奇特的物质之一,不仅滋润万物,更是变化万千!你很难想象冰、水、水蒸气其实是同一个东西H2O,看到冰你可能会联想到玻璃、石头,看到水你可能会联想到牛奶、可乐,看到水蒸气你可能会联想到空气、氧气。三个不同状态下的水好像是三种不同的东西。

水的状态变化万千,程序也可以实现万千的功能。那么如何用程序来模拟水的三种不同状态及相互转化呢?

我们从对象的角度来考虑会有哪个类,首先不管它是什么状态,对象始终是水(H2O),所以会有一个Water类;而它又有三种状态,我们可以定义三个状态类:SolidState、LiquidState、GaseousState;从SolidState、LiquidState、GaseousState这三个单词中我们会发现都有一个State后缀,于是我们会想它们之间是否有一些共性,能否提取出一个更抽象的类,这个类就是状态类(State)。这些类之间的关系可用图表示,如图2-1所示。

图2-1 水的三态相关类之间的关系

好了,我们已经知道了大概的关系,开始编码实现吧,在实现的过程中不断完善。

源码示例2-1 模拟故事剧情

测试代码:

输出结果:

2.2 从剧情中思考状态模式

2.2.1 什么是状态模式

Allow an object to alter its behavior when its internal state changes.The object will appear to change its class.

允许一个对象在其内部状态发生改变时改变其行为,使这个对象看上去就像改变了它的类型一样。

如水一般,状态即事物所处的某一种形态。状态模式是说一个对象在其内部状态发生改变时,其表现的行为和外在属性不一样,这个对象看上去就像改变了它的类型一样。因此,状态模式又称为对象的行为模式。

2.2.2 状态模式设计思想

从故事剧情的示例中我们知道,水有三种不同状态:冰、水、水蒸气。三种不同的状态有着完全不一样的外在特性:冰,质坚硬,无流动性,表面光滑;水,具有流动性;水蒸气,质轻,肉眼看不见,却存在于空气中。这三种状态的特性是不是相差巨大?简直就不像是同一种东西,但事实却是不管它在什么状态,其内部组成都是一样的,都是水分子(H2O)。这也许就是水的至柔至刚之道吧!

状态模式的核心思想就是一个事物(对象)有多种状态,在不同的状态下所表现出来的行为和属性不一样。

2.3 状态模式的模型抽象

2.3.1 代码框架

模拟故事剧情的代码(源码示例2-1)还是相对比较粗糙的,也有一些不太合理的实现,如:

(1)Water的setTemperature(self,temperature)方法不符合程序设计中的开放封闭原则。虽然水只有三种状态,但在其他的应用场景中可能会有更多的状态,如果要再加一个状态(State),则要在SetTemperature中再加一个if else判断。

(2)表示状态的类应该只会有一个实例,因为不可能出现“固态1”“固态2”的情形,所以状态类的实现要使用单例,关于单例模式会在第5章中进一步讲述。

针对这些问题,我们可以对它进行进一步的重构和优化,抽象出状态模式的框架模型。

源码示例2-2 状态模式的框架模型

2.3.2 类图

源码示例2-2的代码框架可用类图表示,如图2-2所示。

图2-2 状态模式的类图

State是抽象状态类(基类),负责状态的定义和接口的统一。StateA和StateB是具体的状态类,如故事剧情中的SolidState、LiquidState 、GaseousState。Context是上下文环境类,负责具体状态的切换。

2.3.3 基于框架的实现

有了上面的代码框架之后,我们要实现示例代码的功能就更简单了。我们假设最开始的示例代码为Version 1.0,下面看看基于框架的Version 2.0吧。

源码示例2-3 Version 2.0的实现

这里只要改一下上面测试代码的第一行就可以了:

读者可以自己跑一下,会发现输出结果和之前的是一样的。

2.3.4 模型说明

1.设计要点

(1)在实现状态模式的时候,实现的场景状态有时候会非常复杂,决定状态变化的因素也非常多,我们可以把决定状态变化的属性单独抽象成一个类 StateInfo,这样判断状态属性是否符合当前的状态isMatch时就可以传入更多的信息。

(2)每一种状态应当只有唯一的实例。

2.状态模式的优缺点

优点:

(1)封装了状态的转换规则,在状态模式中可以将状态的转换代码封装在环境类中,对状态转换代码进行集中管理,而不是分散在一个个业务逻辑中。

(2)将所有与某个状态有关的行为放到一个类中(称为状态类),使开发人员只专注于该状态下的逻辑开发。

(3)允许状态转换逻辑与状态对象合为一体,使用时只需要注入一个不同的状态对象即可使环境对象拥有不同的行为。

缺点:

(1)会增加系统类和对象的个数。

(2)状态模式的结构与实现都较为复杂,如果使用不当容易导致程序结构和代码的混乱。

2.4 应用场景

(1)一个对象的行为取决于它的状态,并且它在运行时可能经常改变它的状态,从而改变它的行为。

(2)一个操作中含有庞大的多分支的条件语句,这些分支依赖于该对象的状态,且每一个分支的业务逻辑都非常复杂时,我们可以使用状态模式来拆分不同的分支逻辑,使程序有更好的可读性和可维护性。