状态变化模式
- 在组件构建过程中,某些对象的状态经常面临变化,如何对这些变化进行有效的管理?同时有维持高层模块的稳定?“状态变化”模式为这一问题提供了一种解决方案。
- 典型模式
- State
- Memento
1. 动机
- 在软件构建过程中,某些对象的状态如果改变,其行为也会随之而发生改变,比如文档出于只读状态,其支持的行为和读写状态支持的行为就可能完全不同
- 如何在运行时根据对象的状态来透明地更改对象的行为?而不会为对象操作和状态转化之间引入紧耦合?
2. 代码示例
比如我们有个网络应用,该应用会根据网络的状态进行一些行为上的调整,如下代码所示
Network_Open,
Network_Close,
Network_Connect,
};
class NetworkProcessor{
NetWorkState state;
public:
void Operation1(){
if (state == Network_Open){ // 如果当前是打开状态,则经历过一系列操作之后,再恢复成关闭状态
// ....
state = Network_Close;
}
else if(state == Network_Close){ // 如果当前是关闭状态,经历过一些操作之后,设置成连接态
// ...
state = Network_Connect;
}
else if (state == Network_Connect){ // 如果当前是连接状态,经历过一些操作之后,设置成打开状态
// ...
state = Network_Open;
}
}
void Operation2(){
if (state == Network_Open){
// ....
state = Network_Connect;
}
else if(state == Network_Close){
// ...
state = Network_Open;
}
else if (state == Network_Connect){
// ...
state = Network_Close;
}
}
void Operation3(){
// ...
}
};
结合上面代码和前面的动机对比理解:
对象的状态如果改变,其行为也会随之而发生改变:Operation1中的if else就已经很清楚的表明了Operation1是根据你的状态不同,而行为不一样,但是在这个过程中它还会改变它的状态
上面代码有什么问题,有前面学习印象的同学,会感觉这个问题似曾相识,这个很像是策略模式里面解决的问题,很多if else的bad Smell,上面出现的很多If else是有关业务状态,策略模式就已经告诉我们,对于这种情况,我们要问个为什么?
上面代码中的枚举类型以后会不会有其他类型出现呢?如果添加了其他的状态,那我之前的代码应该怎么修改,必然是往里面继续添加else if,然后还要梳理新添加的状态和当前已经存在状态的关系,这明显违背了开闭原则
3. 代码改进
我们首先遵从策略模式里面的类似方式,试下是否可行,我们先提抽象基类
// 这里其实就是将前面的枚举进行类型化的
class NetworkState{
public:
NetworkState *pNext;
virtual void Operation1() = 0;
virtual void Operation2() = 0;
virtual void Operation3() = 0;
NetworkState(){}
virtual ~NetworkState(){}
};
// 这里的代码其实就是将判断当前时候是open态的那部分逻辑抽离成了类,即Open态的类
class OpenState : public NetworkState{
static NetworkState *m_instance;
public:
static NetworkState* getInstance(){
if (m_instance != nullptr){
m_instance = new OpenState();
}
return m_instance;
}
void Operation1(){
// ....这段省略逻辑其实就是前面
// void Operation1(){
// if (state == Network_Open){
// 中省略的逻辑的逻辑
pNext = CloseState::getInstance();
}
void Operation2(){
// ...
pNext = ConnectState::getInstance();
}
void Operation3(){
// ...
}
};
// 下面两个类和前面这个类似
class CloseState : public NetworkState{
};
class ConnectState : public NetworkState{
};
// 网络应用这一层,塞的就不是一个枚举了,而是一个真正的状态对象
class NetworkProcessor{
NetworkState *pState; // 1. 其实这里,和前面的结果是一样的,只是调用的方法变了而已
public:
NetworkProcessor(NetworkState *pState){
this->pState = pState;
}
void Operation1(){
// ..
pState->Operation1(); // 2. 再看这句话,其实虚函数的本质就是运行时的if else即,如果pState这个指针指向的是open态,那么就会调用Open态的Operation1
pState = pState->pNext; // 3. 执行完后,就将它的状态修改为它的下一个状态,这个下一个状态是当前状态对象里面决定的
// ...
}
void Operation2(){
// ..
pState->Operation2();
pState = pState->pNext;
// ...
}
void Operation3(){
// ..
pState->Operation3();
pState = pState->pNext;
// ...
}
这样做了之后好处是什么,其实和策略模式好处异曲同工,当我们状态增加的时候,其实我们只要增加一个类就可以了,比如增加了一个wait状态,只需要增加一个类似下面的类,并且要在这个类里面维护自己的下一个状态,和OpenState一样,像NetworkProcessor这个类完全不需要修改
class WaitState : public NetworkState{
};
4. 模式定义
允许一个对象在其内部状态改变的时候改变它的行为(其实里里面使用的是多态,即前面代码中NetworkProcessor中维护的NetworkState *pState;)。从而使对象看起来似乎修改了其行为。
------《设计模式》GOF
5. 结构
State里面一般是多个行为而不是一个Handle(),当然也可以是一个行为,看起来一个行为的时候和策略模式没有什么两样
6. 要点总结
- State模式将所有与一个特定状态相关的行为(Operation1、2、3)都放入到一个State的子类对象中,在对象状态切换时,切换相应的对象;但同时维持State的接口,这样实现了具体操作于状态转换之间的解耦。
- 为不同的状态引入不同的对象使得状态转换变得更加明确,而且可以保证不会出现状态不一致的情况,因为转换是原子性的---即要么彻底装换过来,要么不转换
(第一版代码各个操作中夹杂着各种状态的切换,很乱很杂,但是我们最后的版本,一个状态只需要关系各个操作之后的下一个状态是什么就好了,比如:operation1之后的状态是什么,2之后的状态是什么,3之后的状态是什么,就很清晰)
- 如果State对象没有实例变量(即没有后继态之后的实例),那么上下文可以共享一个State对象(代码中的单例),从而节省对象开销。