又是一种优化过于臃肿 if...else...
语句的方法~
意图
允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。
动机
想象一个场景, 以马路边行人过马路红绿灯为例子, 红灯亮 30 秒后亮 3 秒黄灯,然后变成亮 20 秒绿灯, 亮完绿灯又是 3 秒黄灯接着红灯这样的循环
如果按照 if...else...
的逻辑, 先判断当前状态是红灯还是绿灯然后决定下一步亮哪一种, 甚至当前是黄灯的时候, 还需要判断前一次是哪种,才能决定, 当然为了方便可以将黄灯分为 红转绿的黄灯, 绿转红的黄灯 2种, 简单用伪代码展示一下逻辑
void switchLight() {
if (当前状态 == 绿灯) {
当前状态 = 绿转红的黄灯;
// 亮黄灯 3 秒;
return 3;
} else if ( 当前状态 == 红灯) {
当前状态 = 红转绿的黄灯;
// 亮黄灯 3 秒;C++
return 3;
} else if ( 当前状态 == 绿转红的黄灯 ){
当前状态 == 红灯;
// 亮红灯 30 秒;
return 30;
} else {
当前状态 == 绿灯;
// 亮绿灯 20 秒;
return 20;
}
}
调用部分的伪代码大概
while (true) {
int wait = switchLight();
sleep(wait);
}
假设现在有个场景, 凌晨基本没有行人通过这个红绿灯, 最大程度的保证机动车通行, 那么红绿灯希望可以一直都是红灯, 或者由原来 30 秒的红灯, 变成 180 秒, 同时绿灯的时间也从 20 秒变成 10 秒, 此时我们就需要改 switchLight()
, 随着实际开发需求的不停改变, 最后可能会出现巨大的条件语句, 导致代码不够清晰, 并且难以修改和扩展
而状态模式的意图就是将 决定状态转移 的逻辑从 if
或者 switch
中剥离出来, 将它们分布到 State
的各个子类中, 将每一个 状态转换 和动作封装到一个类中
UML 图
Context
(环境)- 定义客户端感兴趣的接口
- 维护一个
ConcreteState
子类的实例,这个实例定义当前状态
State
(状态)- 定义一个接口以封装与
Context
的一个特定状态相关的行为
- 定义一个接口以封装与
ConcreteState subclasses
(具体状态子类)- 每一个子类实现一个与
Context
的一个状态相关的行为
- 每一个子类实现一个与
简单说一下这些类协作流程
Context
是客户端使用的主要接口, 客户可用状态对象来配置一个Context
, 一旦一个Context
配置完毕, 它的客户不再需要直接与状态对象打交道Context
将与状态相关的请求委托给当前的ConcreteState
对象处理。Context
和ConcreteState
都可以决定状态改变的规则, 以及在何种条件下进行状态转换
状态模式的意义就是让客户端在整个过程中, 只操作 Context
, 状态的改变交给 State
类及其子类, 那么 Context
就需要增加一个接口, 让 State
对象显式地设定 Context
的当前状态, 但是这样意味 State
子类至少拥有一个其他子类的信息, 这将在各个子类之间产生了实现的依赖
应用场景
- 一个对象的行为取决于它的状态, 并且它必须在运行时刻根据状态改变它的行为。
- 一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态。这个状态通常用一个或多个枚举常量表示。通常 , 有多个操作包含这一相同的条件结构。状态模式将每一个条件分支放入一个独立的类中。这使得你可以根据对象自身的情况将对象的状态作为一个对象,这一对象可以不依赖于其他对象而独立变化。
举个例子
这里就使用红绿灯这个例子, 接下来主要看一下各个部分的实现细节, 首先是 State
这个基类
// state.h
class Context;
class State
{
public:
State();
virtual ~State();
int Handle(Context *context);
void setNextState(State *state);
private:
virtual int doSomething() =0;
private:
State *m_nextState;
};
// state.cpp
#include "state.h"
#include "context.h"
State::State() {}
State::~State() {}
int State::Handle(Context *context)
{
int duration = doSomething();
context->setState(m_nextState);
return duration;
}
void State::setNextState(State *state)
{
m_nextState = state;
}
接着是红绿灯各个状态的子类实现
#include "state.h"
#include <iostream>
using namespace std;
class RedState : public State
{
private:
int doSomething() {
cout << "RedLight ";
return 30;
}
};
class GreenState : public State
{
private:
int doSomething() {
cout << "GreenLight ";
return 20;
}
};
class YellowState : public State
{
private:
int doSomething() {
cout << "YellowLight ";
return 3;
}
};
然后是供客户端的调用的 Context
// context.h
#include <list>
class State;
class Context
{
public:
Context();
~Context();
int Handle();
private:
friend class State;
void setState(State *state);
private:
std::list<State *> m_allStates;
State *m_currentState;
};
// context.cpp
#include "context.h"
#include "state.h"
#include "concretestate.h"
template <typename Iterator>
void DeleteElement(Iterator begin, Iterator end)
{
while (begin != end) {
delete *begin;
++begin;
}
}
template <typename Container>
inline void DeleteAll(const Container &c)
{
DeleteElement(c.begin(), c.end());
}
Context::Context()
{
RedState *red_state = new RedState;
GreenState *green_state = new GreenState;
YellowState *rtog_state = new YellowState;
YellowState *gtor_state = new YellowState;
red_state->setNextState(rtog_state);
green_state->setNextState(gtor_state);
rtog_state->setNextState(green_state);
gtor_state->setNextState(red_state);
m_allStates.push_back(red_state);
m_allStates.push_back(green_state);
m_allStates.push_back(rtog_state);
m_allStates.push_back(gtor_state);
m_currentState = red_state;
}
Context::~Context()
{
DeleteAll(m_allStates);
}
void Context::setState(State *state)
{
m_currentState = state;
}
int Context::Handle()
{
return m_currentState->Handle(this);
}
最后是真正的调用环节 main
部分
#include "context.h"
#include "windows.h"
#include <iostream>
using namespace std;
int main()
{
Context trafficLights;
while (true) {
int wait = trafficLights.Handle();
cout << "Lighting : " << wait << " second" << endl;
// Sleep(wait*1000);
Sleep(1000);
}
return 0;
}
最后的运行结果:
RedLight Lighting : 30 second
YellowLight Lighting : 3 second
GreenLight Lighting : 20 second
YellowLight Lighting : 3 second
RedLight Lighting : 30 second
YellowLight Lighting : 3 second
GreenLight Lighting : 20 second
YellowLight Lighting : 3 second
...
这个例子的实现在一些实现细节上是有一定取舍的,我着重介绍一下实现细节
谁定义状态转换
例子代码中, 状态转换的规则是在 Context
在创建 ConcreteState
后通过 State
的 setNextState()
函数将状态转换的流程定义了下来, 最后 State
这个基类通过 Context
的 setState()
函数来显式的转换
简单点说:Context
拥有所有子类的信息, 并且定义了状态转换的逻辑,统筹了 ConcreteState
, 虽然实际上还是由 ConcreteState
来切换,但是避免了一个 Sate
子类至少拥有一个其他子类的信息,从而子类间不存在实现依赖的情况
例子中的状态转换 A->B->C
很简单,实际情况中可能并不是 A->B
这种情况,可能 A
在一些情况下可以变成 B
, 在另一些情况下变成 C
, 或者 State
存在多种行为,比如 HandleA()
, HandleB()
, HandleC
, 在 A
状态下
- 执行
HandleA()
报错 - 执行
HandleB()
, 切换成B
状态 - 执行
HandleA()
, 切换成C
状态
那么就不能简单的通过 setNextState()
这种方式(当然这是我的实现思路),所以举的例子的实现方式可以进一步改进, 比如 Context
提供返回它创建的 ConcreteState
的接口,代码大致是
void State::Handle(Context *context)
{
if (condition) {
dosomethingA();
context->setState(context->getConcreteStateA());
} else {
doSomethingB();
context->setState(context->getConcreteStateB());
}
}
而且状态设置的实例对象可以交给 State
子类去创建, 也就是类似于下面这样 ConcreteStateA
切换到 ConcreteStateB
,这样 Context
只需要知道初始状态,其它的都可以不必关心, 但是 ConcreteStateA
就需要知道 ConcreteStateB
对象,也就存在了实现依赖
// class ConcreteStateA
void ConcreteStateA::Handle(Context *context)
{
doSomething();
context->setState(new ConcreteStateB());
}
管理 State
对象
例子中 State
对象们被管理在 Context
的 std::list<State *> m_allStates;
对象中, 并不会随着切换一个新的 State
对象而销毁,这样的好处是,Context
预先一次付清创建各个状态对象的开销,如果状态切换改变很频繁,这种方式挺好的
当将要进入的状态在运行时是不可知的, 并且上下文不经常改变状态时, 可仅当需要 State
对象时才创建它们并随后销毁它们, 有点类似于 setState(new ConcreteStateB())
的形式,不过一定要防止内存泄漏的问题
当然管理 State
对象方法还是很多的,比如使用 享元模式(Flyweight) 或者 单例模式(Singleton)
总结
封装 基于状态 的行为,并将行为委托到当前状态,这个思路还是很有意思的~