应对复杂“变态”过程的利器。
【注】这里所谓的“变态”指的是改变状态。
改变目标实体状态时,除了设置状态值,还需考虑原始状态(或其他已有情况),并作相应处理。
例如地铁在启动时,首先只能在停止状态下才能启动,其次还要检查车门是否完全关闭,人员是否安全等等。
如果不采用状态机,那么就要在实现方法中写一大堆的if-else,switch-case。非常的不OO。
最近我们有一个卡券的项目。卡券券码生成后具有四种状态:初始(INIT)、启用(OPEN)、绑定(BOUND)、关闭(CLOSE)。
这四种状态之间的转换有点儿复杂,转换之前首先要考虑源状态,并不是所有状态之间都能转换。例如,对于已经绑定(BOUND,已发给会员)的券码,不能再作任何转换(处于简化的目的,这里没考虑会员过期未使用的情况);已启用(OPEN)的券码可以绑定或关闭,但不能再初始化;已关闭的券码,也不能再作任何操作。
有一种近乎直觉的实现方式是直接为券码提供4个API,init,open,bind,close。但这样作有三个缺点:
- 一是不得不在每个方法中作很多的if-else,switch-case判断,不够优雅
- 二是要设置很多方法,每种目标状态都要设置一个,如果实体的状态很多,必然会导致方法“爆炸”
- 三是会违反RESTful原则。状态的改变需要一个动作,这就不得不在接口的URL上引入动词,例如
/couponcode/open/{id}
,/couponcode/bind/{id}
等等。
理想的方案是:实体知道自己的状态,并能针对目标状态自动做出相应的处理。
这就是状态机:
- 为每种源状态设置一个“状态对象”,并为该对象的设置几种“状态转换方法”(对应各种目标状态),分别用来表示要达到每种目标状态,该对象要采取的动作
- 使用委托模式(delegate),将对实体的处理委托给上述“状态对象”,即改变实体状态的具体动作由状态对象完成
- 使用工厂模式,根据当前实体的状态生成状态对象
- 状态对象还有一个额外的方法,根据目标状态调用对应的“状态转换方法”
- 对外只使用一个update接口,该接口只要接受一个实体对象即可,表示该实体要改变的状态;其实现方法是获取该实体的当前状态,调用工厂获取状态对象,然后执行状态转换方法即可。
ICouponCodeState
是所有状态对象的接口,定义状态转换方法AbstractCouponCodeState
提供方法的缺省实现,默认会抛出异常,表示不支持这种转变CouponCodeInit(Open,Bound,Close)State
对象继承AbstractCouponCodeState
,是具体的状态对象实现类,只Override支持的转换,不支持的转换就由基类的方法抛出异常CouponCodeStateFactory
根据源状态生成具体的状态对象CouponCodeService
的update方法只要调用工厂方法获取状态对象,并执行即可