例子
假设我们要用程序实现一个糖果机,糖果机有如下几个动作:投入25分钱(Insert Quarter),弹出25分钱(Eject Quarter),转动手柄(Turn Crank),释放糖果(Dispense);糖果机出有如下的几个状态:无25分钱(No Quarter),有25分钱(Has Quarter),售出糖果(Sold),糖果售罄(Sold Out)。这些状态与动作的转换关系如下图所示。
糖果机一开机就自动进行“NoQuarter”状态,当有客户投入25分钱时,糖果机会自动进行HasQuarter状态,如果此时客户不想买糖果了,于是执行弹出25分钱的动作,此时糖果机就会弹出机内的25分钱并进入NoQuarter状态;如果客户最终决定购买糖果,在HasQuarter状态时,转动糖果机的曲柄,糖果机会进入Sold状态,此时客户点击释放糖果,糖果机就会释放糖果并统计机内的糖果数量,如果数量大于0,则糖果机会返回到NoQuarter状态,如果机内糖果数量为0,则表示糖果已售罄,糖果机进入SoldOut状态。
为了实现上述的糖果机,设计出如下的类图。
有一个状态的抽象类,其定义了糖果机的动作类方法,其子类分别对应糖果机的四种状态,每种状态下,分别定义了该状态下糖果机的四个动作如何实现,即不同状态下,执行相同的动作,会有不同的效果。如在HasQuarter状态下进行“弹出25分钱”的动作,糖果机会弹出25分钱并转换到NoQuarter状态,如果在Quarter状态下进行“弹出25分钱”的动作,那糖果机并不会弹出25分钱,同时它会弹出一个警告信息:“你逗我呢?你都没有投钱,还想我吐钱,你还真当我是提款机啊!!!”。如此类推。
然后类GumballMachine表示一个糖果机,它的成员变量m_mapStateTypeToStatePtr中存储了糖果机的四种状态,即四个状态子类的对象,m_curStatePtr用于存放糖果机当前的状态,类中定义了糖果机的四个动作,当有人调用这些动作时,糖果机就会直接调用当前状态对象所对应的动作,并作当前状态的转换。所以GumballMachine糖果机的内部,它会自动管理自己的当前状态,并根据当前状态,对糖果机的动作作出相应的应答,并进行状态的转换。
具体实现的代码(C++)见如下链接:https://gitee.com/sujiewen/design-pattern-learning/tree/master/ch10
状态模式
上述的例子就是一个典型的状态模式,状态模式的通用类图如下所示。其定义如下:**当对象内在状态改变时允许其改变行为, 这个对象看起来就像类型被改变了一样。**状态模式的核心是状态的改变导致Context行为的改变,就像这个对象改变了自己的类一样。状态模式中,
- State:抽象类表示抽象状态的角色,其定义所有状态的共同接口,其所有的子类(子状态)都需要实现这些接口;
- ConcreteState:即具体的子状态,这些状态用于处理Context的请求,并且每个具体状态类都实现了自己对于这些请求的具体实现,所以当Context的状态改变时,这些具体的实现也会改变。
- Context:上下文类,它拥有内部状态,这些状态由ConcreteState(具体状态类)来定义,并定义了一些方法来让用户进行请求操作,当用户调用了request方法时,这些请求就会委托到具体状态类来处理。
状态模式的类图与策略模式的类图基本是一样的, 虽然两者类图一样,但用法即不一样。在策略模式中,是Context的用户来选择使用哪一个子类,而在状态模式中,将一系列行为的不同实现封装到各个具体状态类中,Context可以将请求委托到这些具体状态对象中的一个来执行,而这些动作的执行,又可能会改变Context的当前状态,随着时间的流逝,动作的不断执行,Context的当前状态会不断改变,其行为也会不断改变,就像变成了另外的一个新类似的,而Context用户根据不需要管理Context的状态,甚至它不需要知道Context的状态。