[设计模式][c++]状态切换模式

转自:http://blog.csdn.net/yongh701/article/details/49154439

 

状态模式也是设计模式的一种,这种设计模式思想不复杂,就是实现起来的代码有点复杂。主要出现在类传递参数上,尤其是C++这种不能直接类间互相调用都语言,实现状态模式更难,当然,一切设计模式都是为了更简短的主函数。

状态模式是当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类,主要解决的是当控制一个对象状态转换的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同的一系列类当中,可以把复杂的逻辑判断简单化。主要有以下三种角色:

1、上下文环境(Context):它定义了客户程序需要的接口并维护一个具体状态角色的实例,将与状态相关的操作委托给当前的Concrete State对象来处理。
2、抽象状态(State):定义一个接口以封装使用上下文环境的的一个特定状态相关的行为。
3、具体状态(Concrete State):实现抽象状态定义的接口。

说是这样的意思:

举个例子来说明吧,如下图:

现在要求再主函数中,直接一行代码context->switch_state();从状态A切到状态B。

这里利用状态模式来实现,具体状态就是状态A与状态B,然后上下文存在一个“转换状态”的方法。之后状态A与状态B共同接口就是抽象状态State。具体实现代码如下:

 

[cpp] view plain copy
 
 print?
  1. #include<iostream>  
  2. using namespace std;  
  3. class Context;//类,上下文,的提前引用,主要是用于通过C++的编译,需要提前声明这个类  
  4. class State{//抽象状态  
  5. public:  
  6.     virtual void switch_state()=0;//其实就是一个接口,转换状态的方法switch_state()全部写在这里  
  7. };  
  8. //具体状态  
  9. //每一个具体状态,都必须有私有变量上下文Context *context;  
  10. //每一个具体状态的构造方法,都必须用this->context=context;实现将自己注册到上下文中。  
  11. //不得在每一个具体状态中实现转换状态的方法switch_state(),只能在类外实现,因为C++禁止类的互相调用,否则会出现error C2027: 使用了未定义类型的错误  
  12. class StateA:public State{  
  13. private:  
  14.     Context *context;  
  15. public:  
  16.     StateA(Context *context){  
  17.         this->context=context;  
  18.     }  
  19.     void switch_state();  
  20. };  
  21. class StateB:public State{  
  22. private:  
  23.     Context *context;  
  24. public:  
  25.     StateB(Context *context){  
  26.         this->context=context;  
  27.     }  
  28.     void switch_state();  
  29. };  
  30. //上下文的实现,里面包含一个设置抽象状态的方法,各个取具体状态的方法。  
  31. /* 
  32.     同时,抽象状态中定义的实现状态方法,这里要有 
  33.     void switch_state(){ 
  34.         state->switch_state(); 
  35.     } 
  36.     的实现,用于暴露给客户端调用 
  37. */  
  38. class Context{  
  39. private:  
  40.     State *stateA,*stateB,*state;  
  41. public:  
  42.     Context(){  
  43.         stateA=new StateA(this);  
  44.         stateB=new StateB(this);  
  45.         this->state=stateA;  
  46.     }  
  47.     void switch_state(){  
  48.         state->switch_state();  
  49.     }  
  50.     void setState(State* state){  
  51.         this->state=state;  
  52.     }  
  53.     State* getStateA(){  
  54.         return stateA;  
  55.     }  
  56.     State* getStateB(){  
  57.         return stateB;  
  58.     }  
  59. };  
  60. //各个具体状态中,所对应转换状态方法。  
  61. void StateA::switch_state(){  
  62.     this->context->setState(this->context->getStateB());//转换到B状态的特定写法  
  63.     cout<<"已转换到状态B"<<endl;  
  64. };  
  65. void StateB::switch_state(){  
  66.     this->context->setState(this->context->getStateA());//转换到A状态的特定写法  
  67.     cout<<"已转换到状态A"<<endl;  
  68. };  
  69. //主函数  
  70. int main(){  
  71.     Context *context=new Context();  
  72.     context->switch_state();  
  73.     context->switch_state();  
  74.     context->switch_state();  
  75.     context->switch_state();  
  76.     return 0;  
  77. }  


运行结果如下图:

 

可以看到,在主函数中,只是初始化了上下文,然而不停调用上下文的switch_state()方法,却在两个具体状态A与B之间跳转。然而,同样的一句switch_state()有着不同实现,打印的内容是不同。

上述代码还有C++的特色,各个具体状态中,所对应转换状态方法,只能在类外实现,而不能在直接在StateA与StateB里面实现。因为C++不像Java,Java编译的时候一次性把所有东西读进去。C++是见一行读一行。这里Context类用到State,StateA与StateB用到了Context,类间相互调用在C++中是不行的。

同时,注意在上下文类Context的构造类,对各个具体状态初始化,也就是注册各个具体状态到上下文,否则编译是过了,却在程序中出现空指针。

那么这种状态模式到底有什么呢?这里用一道2011年下半年的软件设计师软考题目再来说明:

题目是这样的:

某大型商场内安装了多个简易的纸巾售卖机,自动出售2元钱一包的纸巾,且每次仅售出一包纸巾,纸巾售卖机的状态图如图5-1所示:

 

采用状态(State)模式来实现该纸巾售卖机,得到如图5-2所示的类图,其中类State为抽象类,定义了投币、退币、出纸巾等方法接口。类SoldOutState、NoQuarterState、HasQuarterState、SoldState分别对应图5-1纸巾售卖机的4种状态。售出纸巾、纸巾售卖、买有投币、有2元钱。

这里很显然,如果不用状态模式,会产生大量的if...else语句,代码将很不容易改变,,难以拓展。状态转换隐藏在条件语句中,所以并不明显未来加入的代码可能导致bug。

那么用状态模式,先来分析一下,这里具体状态有4个,分别对应4个类,TissueMachine类就是开放给主函数的上下文,而用户能够操作的地方,有3个,一个是投币、一个是退币,另一个是按“出纸巾”,这是上下文TissueMachine能给主函数调用的方法就这三个。而没有提到的售出方法dispense,是上下文自身内部的状态装换,因此只在售出纸巾这个状态中实现这个方法。不过,由于抽象状态State定义了这4个方法的接口,因此,4个具体状态都要有这4个方法,当然具体实现因状态不同而不同,具体代码如下:

 

[cpp] view plain copy
 
 print?
  1. #include<iostream>  
  2. using namespace std;  
  3. //以下为类的定义部分  
  4. class TissueMachine;//类的提前引用  
  5. //抽象状态  
  6. class State{  
  7. public:  
  8.     virtual void insertQuarter()=0;//“投币”按钮被按下  
  9.     virtual void ejectQuarter()=0;//“退币”按钮被按下  
  10.     virtual void turnCrank()=0;//“出纸巾”按钮被按下  
  11.     virtual void dispense()=0;//正在卖出纸巾  
  12. };  
  13.   
  14. //具体状态  
  15. class SoldOutState:public State{//纸巾售完状态  
  16. private:  
  17.     TissueMachine* tissueMachine;  
  18. public:  
  19.     SoldOutState(TissueMachine *tissueMachine){  
  20.         this->tissueMachine=tissueMachine;  
  21.     }  
  22.     void insertQuarter();  
  23.     void ejectQuarter();  
  24.     void turnCrank();  
  25.     void dispense();  
  26. };  
  27. class NoQuarterState:public State{//没有投币状态  
  28. private:  
  29.     TissueMachine* tissueMachine;  
  30. public:  
  31.     NoQuarterState(TissueMachine *tissueMachine){  
  32.         this->tissueMachine=tissueMachine;  
  33.     }  
  34.     void insertQuarter();  
  35.     void ejectQuarter();  
  36.     void turnCrank();  
  37.     void dispense();  
  38. };  
  39. class HasQuarterState:public State{//有2元钱(已投币状态)  
  40. private:  
  41.     TissueMachine* tissueMachine;  
  42. public:  
  43.     HasQuarterState(TissueMachine *tissueMachine){  
  44.         this->tissueMachine=tissueMachine;  
  45.     }  
  46.     void insertQuarter();  
  47.     void ejectQuarter();  
  48.     void turnCrank();  
  49.     void dispense();  
  50. };  
  51. class SoldState:public State{//出售纸巾状态  
  52. private:  
  53.     TissueMachine* tissueMachine;  
  54. public:  
  55.     SoldState(TissueMachine *tissueMachine){  
  56.         this->tissueMachine=tissueMachine;  
  57.     }  
  58.     void insertQuarter();  
  59.     void ejectQuarter();  
  60.     void turnCrank();  
  61.     void dispense();  
  62. };  
  63.   
  64. //上下文  
  65. class TissueMachine{  
  66. private:  
  67.     State *soldOutState,*noQuarterState,*hasQuarterState,*soldState,*state;  
  68.     int count;//纸巾数  
  69. public:  
  70.     TissueMachine(int numbers){//构造函数,定义初始状态有纸巾售卖机有多少纸巾  
  71.         soldOutState=new SoldOutState(this);  
  72.         noQuarterState=new NoQuarterState(this);  
  73.         hasQuarterState=new HasQuarterState(this);  
  74.         soldState=new SoldState(this);  
  75.         this->count=numbers;  
  76.         if (count> 0) {    
  77.             this->state=noQuarterState;//开始为没有投币的状态  
  78.         }  
  79.     };  
  80.     //开放给主函数调用的方法  
  81.     void insertQuarter(){  
  82.         state->insertQuarter();  
  83.     }  
  84.     void ejectQuarter(){  
  85.         state->ejectQuarter();  
  86.     }  
  87.     void turnCrank(){  
  88.         state->turnCrank();  
  89.         state->dispense();  
  90.     }  
  91.     //数据传递的getter与setter  
  92.     void setState(State* state){  
  93.         this->state=state;  
  94.     }  
  95.     State* getHasQuarterState(){  
  96.         return hasQuarterState;  
  97.     }  
  98.     State* getNoQuarterState(){  
  99.         return noQuarterState;  
  100.     }  
  101.     State* getSoldState(){  
  102.         return soldState;  
  103.     }     
  104.     State* getSoldOutState(){  
  105.         return soldOutState;  
  106.     }  
  107.     int getCount(){  
  108.         return count;  
  109.     };  
  110.     void setCount(int numbers){  
  111.         this->count=numbers;  
  112.     };  
  113. };  
  114.   
  115. //具体状态中各个方法的具体实现。  
  116. //纸巾售完状态  
  117. void SoldOutState::insertQuarter(){       
  118.     cout<<"机器无纸巾,已退回硬币!"<<endl;  
  119. }  
  120. void SoldOutState::ejectQuarter(){  
  121.     cout<<"自动售货机根本没有硬币!"<<endl;  
  122. }  
  123. void SoldOutState::turnCrank(){  
  124.     cout<<"机器无纸巾,请不要操作机器"<<endl;  
  125. }  
  126. void SoldOutState::dispense(){  
  127. }  
  128. //没有投币状态  
  129. void NoQuarterState::insertQuarter(){         
  130.     tissueMachine->setState(tissueMachine->getHasQuarterState());  
  131.     cout<<"已投币!"<<endl;  
  132. }  
  133. void NoQuarterState::ejectQuarter(){  
  134.     cout<<"自动售货机根本没有硬币!"<<endl;  
  135. }  
  136. void NoQuarterState::turnCrank(){  
  137.     cout<<"请投币"<<endl;  
  138. }  
  139. void NoQuarterState::dispense(){  
  140. }  
  141. //有2元钱(已投币状态)  
  142. void HasQuarterState::insertQuarter(){  
  143.     cout<<"已投币!请不要重复投币!已退回重复投币!"<<endl;  
  144. }  
  145. void HasQuarterState::ejectQuarter(){  
  146.     tissueMachine->setState(tissueMachine->getNoQuarterState());  
  147.     cout<<"已取币!"<<endl;  
  148. }  
  149. void HasQuarterState::turnCrank(){  
  150.     tissueMachine->setState(tissueMachine->getSoldState());  
  151.     cout<<"请等待自动售货机出纸巾!"<<endl;  
  152. }  
  153. void HasQuarterState::dispense(){  
  154. }  
  155. //出售纸巾状态  
  156. void SoldState::insertQuarter(){  
  157.     cout<<"请等待自动售货机出纸巾!请不要投币!已退回投币!"<<endl;  
  158. }  
  159. void SoldState::ejectQuarter(){  
  160.     tissueMachine->setState(tissueMachine->getNoQuarterState());  
  161.     cout<<"请等待自动售货机出纸巾!无法取回已消费的硬币!"<<endl;  
  162. }  
  163. void SoldState::turnCrank(){  
  164.     cout<<"请等待自动售货机出纸巾!已响应你的操作!"<<endl;  
  165. }  
  166. void SoldState::dispense(){//售出纸巾动作  
  167.     if(tissueMachine->getCount()>0){  
  168.         tissueMachine->setState(tissueMachine->getNoQuarterState());  
  169.         tissueMachine->setCount(tissueMachine->getCount()-1);  
  170.         cout<<"你的纸巾,请拿好!"<<endl;  
  171.     }  
  172.     else{  
  173.         tissueMachine->setState(tissueMachine->getSoldOutState());  
  174.         cout<<"已退回你的硬币!纸巾已卖光,等待进货!"<<endl;  
  175.     }  
  176. }     
  177. //主函数  
  178. int main(){  
  179.     TissueMachine *tissueMachine=new TissueMachine(1);  
  180.     cout<<"纸巾数:"<<tissueMachine->getCount()<<endl;  
  181.     tissueMachine->insertQuarter();//投币  
  182.     tissueMachine->turnCrank();//取纸巾  
  183.     cout<<"纸巾数:"<<tissueMachine->getCount()<<endl;//不投币取纸巾测试  
  184.     tissueMachine->turnCrank();   
  185.     cout<<"纸巾数:"<<tissueMachine->getCount()<<endl;//售完纸巾,投币取纸巾测试  
  186.     tissueMachine->insertQuarter();    
  187.     tissueMachine->turnCrank();   
  188.     return 0;  
  189. }  


运行结果如下:

 

这里设置纸巾机一开始仅有1个纸巾,分别做不同的测试,可见纸巾自动售货机有不同的响应。

posted @ 2017-06-22 13:53  南水之源  阅读(849)  评论(1编辑  收藏  举报