在实际开发中,我们经常会遇到这种情况;一个对象有多种状态,在每一个状态下,都会有不同的行为。那么在代码中我们经常是这样实现的。
typedef enum tagState { state, state1, state2 }State; void Action(State actionState) { if (actionState == state) { // DoSomething } else if (actionState == state1) { // DoSomething } else if (actionState == state2) { // DoSomething } else { // DoSomething } }
而这种就好比简单工厂模式,当我们增加新的状态类型时,我们又需要修改原来的代码,这种对于测试是很不利的;由于简单工厂的缺点那么的明显,后来的工厂模式就克服了这个缺点,我们就可以借鉴工长模式,来解决这种随着状态增加而出现的多分支结构。
在GOF的《设计模式:可复用面向对象软件的基础》一书中对状态模式是这样说的:允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。状态模式的重点在于状态转换,很多时候,对于一个对象的状态,我们都是让这个对象包含一个状态的属性,这个状态属性记录着对象的具体状态,根据状态的不同使用分支结构来执行不同的功能,就像上面的代码那样处理;就像上面说的,类中存在大量的结构类似的分支语句,变得难以维护和理解。状态模式消除了分支语句,就像工厂模式消除了简单工厂模式的分支语句一样,将状态处理分散到各个状态子类中去,每个子类集中处理一种状态,这样就使得状态的处理和转换清晰明确。或者说,每个状态有着相应的行为.
使用场合
不只是根据状态,也有根据属性.如果某个对象的属性不同,对象的行为就不一样,这点在数据库系统中出现频率比较高,我们经常会在一个数据表的尾部,加上property属性含义的字段,用以标识记录中一些特殊性质的记录,这种属性的改变(切换)又是随时可能发生的,就有可能要使用State.在以下两种情况下均可使用State模式:
当状态数目不是很多的时候,switch/case可能可以搞定,但是当数目很多的时候(实际系统中也正是如此),维护一大组的switch/case语句将是一件异常困难并且出差的事情。状态逻辑和动作实现没有分离。在很多的系统实现中,动作的实现代码直接写在状态的逻辑当中。这带来的后果就是系统的扩展性和维护得不到保证。
state模式就是被用来解决上面列出的两个问题,在state模式中我们将状态逻辑和动作实现进行分离。当一个操作中要维护大量的case分支语句,并且这些分支依赖对象的状态。state模式将每一个分支都封装到独立的类中。
允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它所属的类。
Context:定义客户端感兴趣的接口,并且维护一个ConcreteState子类的实例,这个实例定义当前状态;
State:定义一个接口以封装与Context的一个特定状态相关的行为;
ConcreteState subclasses:每一个子类实现一个与Context的一个状态相关的行为。
它们之间的协作步骤如下:
state模式采用了对这些不同状态进行封装的方式处理这类问题,当状态改变的时候进行处理然后再切换到另一种状态,也就是说白状态的切换责任交给了具体的状态去负责。
state模式和strategy模式在图示上有很多相似的地方。需要说明的是两者的思想都是一致的。只不过封装的东西不同:state模式封装的是不同的状态,而strategy模式封装的是不同的算法。
#include <iostream> using namespace std; class Context; class State { public: virtual void Handle(Context *pContext) = 0; }; class ConcreteStateA : public State { public: virtual void Handle(Context *pContext) { cout<<"I am concretestateA."<<endl; } }; class ConcreteStateB : public State { public: virtual void Handle(Context *pContext) { cout<<"I am concretestateB."<<endl; } }; class Context { public: Context(State *pState) : m_pState(pState){} void Request() { if (m_pState) { m_pState->Handle(this); } } void ChangeState(State *pState) { m_pState = pState; } private: State *m_pState; }; int main() { State *pStateA = new ConcreteStateA(); State *pStateB = new ConcreteStateB(); Context *pContext = new Context(pStateA); pContext->Request(); pContext->ChangeState(pStateB); pContext->Request(); delete pContext; delete pStateB; delete pStateA; }state及其子类中的操作都将Context*传入作为参数,其主要目的是state类可以通过这个指针调用Context中的方法。这也是state模式和strategy模式最大的区别。
state模式和strategy模式的区别在于它们所关注的点不尽相同:state模式主要是要适应对象对于状态改变时的不同处理策略的实现,而strategy则主要是具体算法和实现接口的解耦,strategy模式中并没有状态的概念(虽然很多时候有可以被看作是状态的概念),并且更加不关心状态的改变。
state模式很好的实现了对象的状态逻辑和动作实现的分离,状态逻辑分布在state的派生类中实现,而动作实现则可以放在context类中实现(这也是为什么state派生类需要拥有一个指向context的指针)。这使得两者的变化相互独立,改变state的状态逻辑可以很容易复用context的动作,也可以不影响state派生类的前提下创建context的子类来更改或替换动作实现。
state模式问题主要是逻辑分散化,状态逻辑分布到了很多的state的子类中,很难看到整个的状态逻辑图,这也带来了代码的维护问题。