设计形式(c++)笔记之十五(State模式)
一、描述
概念:允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。
问题:
每个人、事物在不同的状态下会有不同表现(动作),而一个状态又会在不同的表现下转移到下一个不同的状态(State)。最简单的一个生活中的例子就是:
地铁入口处,如果你放入正确的地铁票,门就会打开让你通过。在出口处也是验票,如果正确你就可以 ok,否则就不让你通过(如果你动作野蛮,或许会有报警(Alarm),:)。
有限状态自动机(FSM)也是一个典型的状态不同,对输入有不同的响应(状态转移)。通常我们在实现这类系统会使用到很多的Switch/Case语句,Case某种状态,发生什么动作,Case 另外一种状态,则发生另外一种状态。但是这种实现方式至少有以下两个问题:
1)当状态数目不是很多的时候,Switch/Case 可能可以搞定。但是当状态数目很多的时候(实际系统中也正是如此),维护一大组的 Switch/Case 语句将是一件异常困难并且容易出错的事情。
2)状态逻辑和动作实现没有分离。在很多的系统实现中,动作的实现代码直接写在状态的逻辑当中。这带来的后果就是系统的扩展性和维护得不到保证。
模式选择
State 模式就是被用来解决上面列出的两个问题的,在 State 模式中我们将状态逻辑和动作实现进行分离。当一个操作中要维护大量的 case 分支语句,并且这些分支依赖于对象的状态。State 模式将每一个分支都封装到独立的类中。
State 模式典型的结构图为:
二、实例
现在城市发展很快,百万级人口的城市一堆一堆的,那其中有两个东西的发明在城市的发展中起到非常重要的作用:一个是汽车,一个呢是...,猜猜看,是什么?是电梯!汽车让城市可以横向扩展,电梯让城市可以纵向延伸,向空中伸展。汽车对城市的发展我们就不说了,电梯,你想想看,如果没有电梯,每天你需要爬 10 层楼梯,你是不是会崩溃掉?建筑师设计了一个没有电梯的建筑,那投资家肯定不愿意投资,那也是建筑师的耻辱呀,今天我们就用程序表现一下这个电梯是怎么运作的。
我们每天都在乘电梯,那我们来看看电梯有哪些动作(映射到c++ 中就是有多少方法):开门、关门、运行、停止,就这四个动作,好,我们就用程序来实现一下电梯的动作,先看类图设计:
我们继续往下分析,这个程序设计有什么问题,你想呀电梯门可以打开,但不是随时都可以开,是有前提条件的的,你不可能电梯在运行的时候突然开门吧?!电梯也不会出现停止了但是不开门的情况吧?!那要是有也是事故嘛,再仔细想想,电梯的这四个动作的执行都是有前置条件,具体点说说在特定状态下才能做特定事,那我们来分析一下电梯有什么那些特定状态:
门敞状态---按了电梯上下按钮,电梯门开,这中间有 5 秒的时间(当然你也可以用身体挡住电梯门,那就不是 5 秒了),那就是门敞状态;在这个状态下电梯只能做的动作是关门动作,做别的动作?那就危险
门闭状态---电梯门关闭了,在这个状态下,可以进行的动作是:开门(我不想坐电梯了)、停止(忘记按路层号了)、运行运行状态---电梯正在跑,上下窜,在这个状态下,电梯只能做的是停止;停止状态---电梯停止不动,在这个状态下,电梯有两个可选动作:继续运行和开门动作;我们用一张表来表示电梯状态和动作之间的关系:
看到这张表后,我们才发觉,哦~~,我们的程序做的很不严谨,好,我们来修改一下,先看类图:
在接口中定义了四个常量,分别表示电梯的四个状态:门敞状态、关闭状态、运行状态、停止状态,然后在实现类中电梯的每一次动作发生都要对状态进行判断,判断是否运行执行,也就是动作的执行是否符合业务逻辑,实现类中的四个私有方法是仅仅实现电梯的动作,没有任何的前置条件,因此这四个方法是不能为外部类调用的,设置为私有方法.(还是存在问题)
刚刚我们是从电梯的有哪些方法以及这些方法执行的条件去分析,现在我们换个角度来看问题,我们来想电梯在具有这些状态的时候,能够做什么事情,也就是说在电梯处于一个具体状态时,我们来思考这个状态是由什么动作触发而产生以及在这个状态下电梯还能做什么事情,举个例子来说,电梯在停止状态时,我们来思考两个问题:
第一、这个停止状态时怎么来的,那当然是由于电梯执行了 stop 方法而来的;
第二、在停止状态下,电梯还能做什么动作?继续运行?开门?那当然都可以了。
我们再来分析其他三个状态,也都是一样的结果,我们只要实现电梯在一个状态下的两个任务模型就可以了:这个状态是如何产生的以及在这个状态下还能做什么其他动作(也就是这个状态怎么过渡到其他状态),既然我们以状态为参考模型,那我们就先定义电梯的状态接口,思考过后我们来看类图(如下类图和之前的做了稍微变化):
本人的工程目录:
注释:
main(),客户
ILift ,电梯接口
lift,电梯实现类
CLiftState,电梯状态抽象类
CCloseingState,电梯门关闭
COpenningState,电梯门打开
CRunningState,电梯运行
CStoppingState,电梯停止
CContext,电梯的控制面板
说明:CContext保持电梯的状态,并提供操作的接口函数。当函数被调用时,CContext直接调用当前状态的相应函数。由状态的接口函数来确定是否可以执行这个动作,以及修改状态为执行这个动作后的状态。
代码:
电梯接口:ILift类
ILift.h
#ifndef State_ILift_h #define State_ILift_h class ILift { public: ILift(void) { } virtual ~ILift(void) { } //定义四个状态:门开、关闭、运行、停止, static const int OPENING_STATE = 1; static const int CLOSING_STATE = 2; static const int RUNNING_STATE = 3; static const int STOPPING_STATE = 4; //设置电梯的状态 virtual void SetState(int state) = 0; //首先电梯门开启动作 virtual void Open() = 0; //电梯门有开启,那当然也就有关闭了 virtual void Close() = 0; //电梯要能上能下,跑起来 virtual void Run() = 0; //电梯还要能停下来 virtual void Stop() = 0; }; #endif
电梯实现类:Lift类
Lift.h
#ifndef __State__Lift__ #define __State__Lift__ #include <iostream> #include "ILift.h" class CLift :public ILift { public: CLift(void); ~CLift(void); void SetState(int state); void Open(); void Close(); void Run(); void Stop(); private: int m_state; void OpenWithoutLogic(); void CloseWithoutLogic(); void RunWithoutLogic(); void StopWithoutLogic(); }; #endif /* defined(__State__Lift__) */Lift.cpp
#include "Lift.h" using std::cout; using std::endl; CLift::CLift(void) { this->m_state = 0; } CLift::~CLift(void) { } void CLift::SetState(int state) { this->m_state = state; } void CLift::Open() { //电梯在什么状态才能开启 switch(this->m_state) { case OPENING_STATE: //如果是开门状态,则什么都不做 break; case CLOSING_STATE: //如果是关门状态,则可以开启 this->OpenWithoutLogic(); this->SetState(OPENING_STATE); break; case RUNNING_STATE: //正在运行状态,则不能开门,什么都不做 break; case STOPPING_STATE: //停止状态,淡然要开门 this->OpenWithoutLogic(); this->SetState(OPENING_STATE); break; } } void CLift::Close() { switch(this->m_state) { //电梯在什么状态才能关闭 case OPENING_STATE: //如果是开门状态,则可以关门 this->CloseWithoutLogic(); this->SetState(CLOSING_STATE); break; case CLOSING_STATE: //如果是关门状态,则什么都不做 break; case RUNNING_STATE: //正在运行状态,门本来就是关闭的,也说明都不做 break; case STOPPING_STATE: //停止状态,本也是关闭的,什么也不做 break; } } void CLift::Run() { //电梯在什么状态才能跑起来 switch(this->m_state) { case OPENING_STATE: //如果是开门状态,则不能运行,什么都不做 break; case CLOSING_STATE: //如果是关闭状态,则可以运行 this->RunWithoutLogic(); this->SetState(RUNNING_STATE); break; case RUNNING_STATE: //如果是运行状态,则什么都不做 break; case STOPPING_STATE: //停止状态,可以运行 this->RunWithoutLogic(); this->SetState(RUNNING_STATE); break; } } void CLift::Stop() { //电梯在什么状态才能停止 switch(this->m_state) { case OPENING_STATE: //如果是开门状态,则不能运行,什么都不做 break; case CLOSING_STATE: //如果是开门状态,则当然可以停止了 this->StopWithoutLogic(); this->SetState(CLOSING_STATE); break; case RUNNING_STATE: //如果是运行状态,有运行当然也就有停止了 this->StopWithoutLogic(); this->SetState(CLOSING_STATE); break; case STOPPING_STATE: //停止状态,什么都不做 break; } } void CLift::OpenWithoutLogic() { cout << "电梯门开启..." << endl; } void CLift::CloseWithoutLogic() { cout << "电梯门关闭..." << endl; } void CLift::RunWithoutLogic() { cout << "电梯上下跑起来..." << endl; } void CLift::StopWithoutLogic() { cout << "电梯停止了..." << endl; }
电梯状态抽象类:LiftState类
LiftState.h
#ifndef __State__LiftState__ #define __State__LiftState__ #include <iostream> class CContext; class CLiftState { public: CLiftState(void); virtual ~CLiftState(void); void SetContext(CContext *pContext); virtual void Open() = 0; virtual void Close() = 0; virtual void Run() = 0; virtual void Stop() = 0; protected: CContext *m_pContext; }; #endif /* defined(__State__LiftState__) */LiftState.cpp
#include "LiftState.h" CLiftState::CLiftState(void) { } CLiftState::~CLiftState(void) { } void CLiftState::SetContext( CContext *pContext ) { m_pContext = pContext; }
电梯门打开:OpenningState类
OpenningState.h
#ifndef __State__OpenningState__ #define __State__OpenningState__ #include <iostream> #include "LiftState.h" class COpenningState : public CLiftState { public: COpenningState(void); ~COpenningState(void); void Open(); void Close(); void Run(); void Stop(); }; #endif /* defined(__State__OpenningState__) */OpenningState.cpp
#include "OpenningState.h" #include "Context.h" #include <iostream> using std::cout; using std::endl; COpenningState::COpenningState(void) { } COpenningState::~COpenningState(void) { } //打开电梯门 void COpenningState::Open() { cout << "电梯门开启..." << endl; } //开启当然可以关闭了,我就想测试一下电梯门开关功能 void COpenningState::Close() { //状态修改 this->CLiftState::m_pContext->SetLiftState(CContext::pCloseingState); //动作委托为CloseState来执行 this->CLiftState::m_pContext->GetLiftState()->Close(); } //门开着电梯就想跑,这电梯,吓死你! void COpenningState::Run() { //do nothing } //开门还不停止? void COpenningState::Stop() { //do nothing }
电梯门关闭:CloseingState类
CloseingState.h
#ifndef __State__CloseingState__ #define __State__CloseingState__ #include <iostream> #include "LiftState.h" class CCloseingState:public CLiftState { public: CCloseingState(void); ~CCloseingState(void); void Open(); void Close(); void Run(); void Stop(); }; #endif /* defined(__State__CloseingState__) */CloseingState.cpp
#include "CloseingState.h" #include "Context.h" using std::cout; using std::endl; CCloseingState::CCloseingState(void) { } CCloseingState::~CCloseingState(void) { } //电梯门关了再打开 void CCloseingState::Open() { this->CLiftState::m_pContext->SetLiftState(CContext::pOpenningState); this->CLiftState::m_pContext->GetLiftState()->Open(); } //电梯门关才,这就关闭状态要实现动作 void CCloseingState::Close() { cout << "电梯门关闭..." << endl; } //电梯门关了就跑 void CCloseingState::Run() { this->CLiftState::m_pContext->SetLiftState(CContext::pRunningState); this->CLiftState::m_pContext->GetLiftState()->Run(); } //电梯门关着,我就不按楼层 void CCloseingState::Stop() { this->CLiftState::m_pContext->SetLiftState(CContext::pStoppingState); this->CLiftState::m_pContext->GetLiftState()->Stop(); }
电梯运行:RunningState类
RunningState.h
#ifndef __State__RunningState__ #define __State__RunningState__ #include <iostream> #include "LiftState.h" class CRunningState : public CLiftState { public: CRunningState(void); ~CRunningState(void); void Open(); void Close(); void Run(); void Stop(); }; #endif /* defined(__State__RunningState__) */RunningState.cpp
#include "RunningState.h" #include "RunningState.h" #include "Context.h" #include <iostream> using std::cout; using std::endl; CRunningState::CRunningState(void) { } CRunningState::~CRunningState(void) { } //运行的时候开电梯门?电梯不会给你开的。 void CRunningState::Open() { //do nothing } //电梯关闭?这是肯定了 void CRunningState::Close() { //do nothing } //具体实现 void CRunningState::Run() { cout << "电梯上下跑..." << endl; } //这个事绝对是合理,光运行不停止还有谁敢做这个电梯? void CRunningState::Stop() { this->CLiftState::m_pContext->SetLiftState(CContext::pStoppingState); this->CLiftState::m_pContext->GetLiftState()->Stop(); }
电梯停止:StoppingState类
StoppingState.h
#ifndef __State__StoppingState__ #define __State__StoppingState__ #include <iostream> #include "LiftState.h" class CStoppingState :public CLiftState { public: CStoppingState(void); ~CStoppingState(void); void Open(); void Close(); void Run(); void Stop(); }; #endif /* defined(__State__StoppingState__) */StoppingState.cpp
#include "StoppingState.h" #include "StoppingState.h" #include "Context.h" using std::cout; using std::endl; CStoppingState::CStoppingState(void) { } CStoppingState::~CStoppingState(void) { } //停止状态,开门,那是要的! void CStoppingState::Open() { this->CLiftState::m_pContext->SetLiftState(CContext::pOpenningState); this->CLiftState::m_pContext->GetLiftState()->Open(); } //停止状态关门?电梯门本来就是关着的! void CStoppingState::Close() { //do nothing } //停止状态再跑起来,正常的很 void CStoppingState::Run() { this->CLiftState::m_pContext->SetLiftState(CContext::pRunningState); this->CLiftState::m_pContext->GetLiftState()->Run(); } //停止状态是怎么发生的呢?当然是停止方法执行了。 void CStoppingState::Stop() { cout << "电梯停止了..." << endl; }
电梯的控制面板:Context类
Context.h
#ifndef __State__Context__ #define __State__Context__ #include <iostream> #include "LiftState.h" #include "OpenningState.h" #include "CloseingState.h" #include "RunningState.h" #include "StoppingState.h" class CContext { public: CContext(void); ~CContext(void); //定义出所有的电梯状态 static COpenningState *pOpenningState; static CCloseingState *pCloseingState; static CRunningState *pRunningState; static CStoppingState *pStoppingState; //定义一个当前电梯状态 CLiftState * GetLiftState(); //把当前的环境通知到各个实现类中 void SetLiftState(CLiftState *pLiftState); void Open(); void Close(); void Run(); void Stop(); private: CLiftState *m_pLiftState; }; #endif /* defined(__State__Context__) */Context.cpp
#include "Context.h" COpenningState* CContext::pOpenningState = NULL; CCloseingState* CContext::pCloseingState = NULL; CRunningState* CContext::pRunningState = NULL; CStoppingState* CContext::pStoppingState = NULL; CContext::CContext(void) { m_pLiftState = NULL; pOpenningState = new COpenningState(); pCloseingState = new CCloseingState(); pRunningState = new CRunningState(); pStoppingState = new CStoppingState(); } CContext::~CContext(void) { delete pOpenningState; pOpenningState = NULL; delete pCloseingState; pCloseingState = NULL; delete pRunningState; pRunningState = NULL; delete pStoppingState; pStoppingState = NULL; } CLiftState * CContext::GetLiftState() { return m_pLiftState; } void CContext::SetLiftState(CLiftState *pLiftState) { this->m_pLiftState = pLiftState; this->m_pLiftState->SetContext(this); } void CContext::Open() { this->m_pLiftState->Open(); } void CContext::Close() { this->m_pLiftState->Close(); } void CContext::Run() { this->m_pLiftState->Run(); } void CContext::Stop() { this->m_pLiftState->Stop(); }
客户:main()
#include <iostream> #include "ILift.h" #include "Lift.h" #include "Context.h" #include "OpenningState.h" #include "CloseingState.h" #include "RunningState.h" #include "StoppingState.h" using std::cout; using std::endl; void DoIt() { //ILift.h, Lift.h, Lift.cpp ILift *pLift = new CLift(); pLift->SetState(ILift::STOPPING_STATE);//电梯的初始条件是停止状态。 pLift->Open();//首先是电梯门开启,人进去 pLift->Close();//然后电梯门关闭 pLift->Run();//再然后,电梯跑起来,向上或者向下 pLift->Stop();//最后到达目的地,电梯停下来 delete pLift; } void DoNew() { //LiftState.h, LiftState.cpp, OpenningState.h, CloseingState.h, RunningState.h, StoppingState.h //Context.h, Context.cpp CContext context; CCloseingState closeingState; context.SetLiftState(&closeingState); context.Close(); context.Open(); context.Run(); context.Stop(); } int main(int argc, const char * argv[]) { cout << "----------使用模式之前----------" << endl; DoIt(); cout << "----------使用模式之后----------" << endl; DoNew(); // insert code here... std::cout << "Hello, World!\n"; return 0; }
结果如下:
参考文献:《设计模式之禅》,《GoF_23种设计模式解析》
参考博客: http://www.cnblogs.com/wanggary/archive/2011/04/21/2024117.html