前言
在dota即时对战中,不同的时期,战争的行为是不一样的。如在游戏战争前期,主要是辅助英雄包鸡包眼游走gank,后期英雄发育为主,一般不会参与团战,这时候主要以前期辅助英雄为主角;在游戏战争中期,后期英雄发育经过前期的发育,开始与辅助英雄抱团推塔,参加团战,辅助作用慢慢减弱,后期英雄慢慢变强;而在游戏战争的后期,经过前期的发育,中期团战金钱的积累,后期英雄成为了战场的主角,开始带领辅助英雄推高地,占领基地。像这种随着时间的推移,角色对象在不同的状态下具有不同的行为或者状态在某些情况下能够相互转换的行为,可以通过状态模式来解决。
状态模式
在状态模式中,我们将对象在每一个状态下的行为和状态转移语句封装在一个个状态类中,通过这些状态类来分散冗长的条件转移语句,让系统具有更好的灵活性和可扩展性
意图 允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类,别名是状态对象
系统中某个对象存在多个状态,这些状态之间可以进行转换,而且对象在不同状态下行为不相同时可以使用状态模式。状态模式将一个对象的状态从该对象中分离出来,封装到专门的状态类中,使得对象状态可以灵活变化,对于客户端而言,无须关心对象状态的转换以及对象所处的当前状态,无论对于何种状态的对象,客户端都可以一致处理
参与者
Context 环境类,拥有多种状态的对象 维护一个抽象状态类State的实例,这个实例定义当前状态,在具体实现时,它是一个State子类的对象
State 抽象状态类,定义一个接口以封装与环境类Context的一个特定状态相关的行为,在抽象状态类中声明了各种不同状态对应的方法 抽象状态类中声明了各种不同状态对应的方法,而在其子类中实现类这些方法,由于不同状态下对象的行为可能不同,因此在不同子类中方法的实现可能存在不同,相同的方法可以写在抽象状态类中
ConcreteState 具体状态类,抽象状态类的子类,每一个子类实现一个与环境类的一个状态相关的行为,每一个具体状态类对应环境的一个具体状态,不同的具体状态类其行为有所不同
模式结构
代码实现 1.首先定义状态上下类StateContext
,维护了一个State
类引用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class StateContext{ private : State *m_pState; int m_value; public: StateContext(State * pState , int value ) : m_pState(pState ) , m_value(value ) {}; public: void Request() { if (!NULL_POINTER(m_pState ) ) { m_pState->Handle(this ) ; } } void SetState(State * pState ) { m_pState = pState; } State* GetState(void ) { return m_pState; } public: int GetValue(void ) { return m_value; } void SetValue(int value ) { m_value = value; } };
2.再定义抽象的状态类State
,并声明Handle()
接口:
1 2 3 4 5 class State { public : virtual void Handle (StateContext *pSC) = 0 ; };
3.再定义两个具体的状态类ConcreteStateA
和ConcreteStateB
,并实现Handle()
接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class ConcreteStateA : public State{ public : virtual void Handle (StateContext *pSC) { cout << "value is " << pSC->GetValue () << endl; cout << "Concrete State A Handle!!!" << endl; } }; class ConcreteStateB : public State{ public : virtual void Handle (StateContext *pSC) { cout << "value is " << pSC->GetValue () << endl; cout << "Concrete State B Handle!!!" << endl; } };
4.测试状态模式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 void StateTest_General() { State *pStateA = new ConcreteStateA() ; State *pStateB = new ConcreteStateB() ; StateContext *pSC = new StateContext(pStateA , 10) ; pSC->Request() ; if (pSC->GetValue() <= 10 ) { pSC->SetState(pStateB ) ; } else { pSC->SetState(pStateA ) ; } pSC->Request() ; SAFE_RELASE_POINTER(pStateA ) ; SAFE_RELASE_POINTER(pStateB ) ; SAFE_RELASE_POINTER(pSC ) ; }
5.运行结果:
value is 10
Concrete State A Handle!!!
value is 10
Concrete State B Handle!!!
改进版 上述,是在客户端中根据上下文信息对状态进行切换,比较好的做法是将状态切换放在状态上下文类或具体的状态类中
改进版1-在StateContext中切换状态 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 class StateContextEx{ private : StateEx *m_pCurrentState; ConcreteStateExA *m_pStateA; ConcreteStateExB *m_pStateB; int m_value; public: StateContextEx(int value = 10) { m_pStateA = new ConcreteStateExA() ; m_pStateB = new ConcreteStateExB() ; m_pCurrentState = m_pStateA; m_value = value; } virtual ~StateContextEx() { SAFE_RELASE_POINTER(m_pStateA ) ; SAFE_RELASE_POINTER(m_pStateB ) ; } public: void SetState(StateEx * pState ) { m_pCurrentState = pState; } void SetValue(int value ) { m_value = value; } int GetValue(void ) { return m_value; } public: void Request() { if (GetValue() < 10 ) { SetState(m_pStateA ) ; } else { SetState(m_pStateB ) ; } m_pCurrentState->Handle() ; } };
改进版2-在具体State中切换状态 1.具体State实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 class ConcreteStateA : public State{ public: virtual void Handle(StateContext * pSC ) { if (pSC->GetValue() < 10 ) { cout << "Concrete State A Handle!!!" << endl; } else if (pSC->GetValue() < 20 ) { pSC->SetState(new ConcreteStateB) ; pSC->GetState() ->Handle(pSC ) ; } else { pSC->SetState(new ConcreteStateC) ; pSC->GetState() ->Handle(pSC ) ; } } }; class ConcreteStateB : public State{ public: virtual void Handle(StateContext * pSC ) { if (pSC->GetValue() < 10 ) { pSC->SetState(new ConcreteStateA) ; pSC->GetState() ->Handle(pSC ) ; } else if (pSC->GetValue() < 20 ) { cout << "Concrete State B Handle!!!" << endl; } else { pSC->SetState(new ConcreteStateC) ; pSC->GetState() ->Handle(pSC ) ; } } }; class ConcreteStateC : public State{ public: virtual void Handle(StateContext * pSC ) { if (pSC->GetValue() < 10 ) { pSC->SetState(new ConcreteStateA) ; pSC->GetState() ->Handle(pSC ) ; } else if (pSC->GetValue() < 20 ) { pSC->SetState(new ConcreteStateB) ; pSC->GetState() ->Handle(pSC ) ; } else { cout << "Concrete State C Handle!!!" << endl; } } };
2.这时候在环境上下文的设置状态接口中要增加释放内存的操作(若是单例模式则不需要):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class StateContext{ private : State *m_pState; int m_value; public: StateContext(State * pState , int value ) : m_pState(pState ) , m_value(value ) {}; public: void SetState(State * pState ) { if (!NULL_POINTER(m_pState ) ) { delete m_pState; } m_pState = pState; }
3.客户端调用:
1 2 3 4 5 6 7 8 9 10 void StateTest_GeneralEx() { StateContextEx *pSCEx = new StateContextEx(5) ; pSCEx->Request() ; pSCEx->SetValue(20) ; pSCEx->Request() ; SAFE_RELASE_POINTER(pSCEx ) ; }
使用场景
对象的行为依赖于它的状态(如某些属性值),状态的改变将导致行为的变化
代码中包含大量与对象状态有关的条件语句,这些条件语句的出现,会导致代码的可维护性和灵活性变差,不能方便地增加和删除状态,并且导致客户类与类库之间的耦合增强
优缺点
优点
封装了状态的转换规则,在状态模式中可以将状态的转换代码封装在环境类或者具体状态类中,可以对状态转换代码进行集中管理
将所有与某个状态有关的行为放到一个类中,只需要注入一个不同的状态对象即可使环境对象拥有不同的行为
允许状态转换逻辑与状态对象合成一体,而不是提供一个巨大的条件语句块,状态模式可以让我们避免使用庞大的条件语句来将业务方法和状态转换代码交织在一起
可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数
缺点
会增加系统中类和对象的个数,导致系统运行开销增大
对“开闭原则”的支持并不太好,增加新的状态类需要修改那些负责状态转换的源代码,否则无法转换到新增状态
状态模式具体实例
Dota游戏战争状态问题 使用状态模式简单实现前言所述的Dota游戏战争状态不同英雄行为不同功能
代码实现 1.定义War上下文类War
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 // War (State Context)class War { private: WarState *m_pWarState; int m_ElapsedDays; public: War(WarState *pWarState) : m_pWarState(pWarState), m_ElapsedDays(0 ) {}; public: int GetElapsedDays() { return m_ElapsedDays; } void SetElapsedDays(int days) { m_ElapsedDays = days; } void SetWarState(WarState *pWarState) { m_pWarState = pWarState; } WarState* GetWarState() { return m_pWarState; } void RunWar() // 具体的War状态类负责状态选择和转换 { m_pWarState->Run(this); } // void RunWar() // 环境变量类负责状态选择和转换 // { // if (GetElapsedDays() < PREPHASE_DAY_MAX) // { // m_pWarState = PrephaseWarState::Instance(); // } // else if (GetElapsedDays() < METAPHASE_DAY_MAX) // { // m_pWarState = MetaphaseWarState::Instance(); // } // else // { // m_pWarState = AnaphaseWarState::Instance(); // } // m_pWarState->Run(); // 无需传入contxt // } };
2.定义抽象的War状态类WarState
,并声明Run()
接口:
1 2 3 4 5 class WarState { public : virtual void Run (War *pWar) = 0 ; };
3.定义具体的三个时期的War状态类后期AnaphaseWarState
,中期MetaphaseWarState
及前期PrephaseWarState
, 实现Run()
接口,并内部根据War进行的时间进行状态选择和切换:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 class AnaphaseWarState : public WarState { private : static AnaphaseWarState *m_pWarState; class Garbage { public: virtual ~Garbage() { if (NULL_POINTER(AnaphaseWarState::Instance() )) { delete AnaphaseWarState::Instance() ; } } }; static Garbage g; private : AnaphaseWarState() {}; AnaphaseWarState(const AnaphaseWarState&) {}; AnaphaseWarState& operator = (const AnaphaseWarState) {}; public: static AnaphaseWarState* Instance() { if (NULL_POINTER(m_pWarState ) ) { m_pWarState = new AnaphaseWarState() ; } return m_pWarState; } public: virtual void Run(War * pWar ) { cout << "Now The War Is On Anaphase State, Anaphase dota heroes become the protagonist, War is coming to end" << "[time = " << pWar->GetElapsedDays() << "]" << endl; } }; AnaphaseWarState* AnaphaseWarState::m_pWarState = NULL; class MetaphaseWarState : public WarState { private : static MetaphaseWarState *m_pWarState; class Garbage { public: virtual ~Garbage() { if (NULL_POINTER(MetaphaseWarState::Instance() )) { delete MetaphaseWarState::Instance() ; } } }; static Garbage g; private : MetaphaseWarState() {}; MetaphaseWarState(const MetaphaseWarState&) {}; MetaphaseWarState& operator = (const MetaphaseWarState) {}; public: static MetaphaseWarState* Instance() { if (NULL_POINTER(m_pWarState ) ) { m_pWarState = new MetaphaseWarState() ; } return m_pWarState; } public: virtual void Run(War * pWar ) { if (pWar->GetElapsedDays() <= METAPHASE_DAY_MAX) { cout << "Now The War Is On Metaphase State, Anaphase and Prephase dota heroes both become the protagonist, War is on hot" << "[time = " << pWar->GetElapsedDays() << "]" << endl; } else { pWar->SetWarState(AnaphaseWarState::Instance() ); pWar->GetWarState() ->Run(pWar ) ; } } }; MetaphaseWarState* MetaphaseWarState::m_pWarState = NULL; class PrephaseWarState : public WarState { private : static PrephaseWarState *m_pWarState; class Garbage { public: virtual ~Garbage() { if (NULL_POINTER(PrephaseWarState::Instance() )) { delete PrephaseWarState::Instance() ; } } }; static Garbage g; private : PrephaseWarState() {}; PrephaseWarState(const PrephaseWarState&) {}; PrephaseWarState& operator = (const PrephaseWarState) {}; public: static PrephaseWarState* Instance() { if (NULL_POINTER(m_pWarState ) ) { m_pWarState = new PrephaseWarState() ; } return m_pWarState; } public: virtual void Run(War * pWar ) { if (pWar->GetElapsedDays() <= PREPHASE_DAY_MAX) { cout << "Now The War Is On Prephase State, Prephase dota heroes become the protagonist, War is coming to begin" << "[time = " << pWar->GetElapsedDays() << "]" << endl; } else { pWar->SetWarState(MetaphaseWarState::Instance() ); pWar->GetWarState() ->Run(pWar ) ; } } }; PrephaseWarState* PrephaseWarState::m_pWarState = NULL;
4.测试状态模式:
1 2 3 4 5 6 7 8 9 10 11 12 void StateTest_War (){ War *pW = new War (PrephaseWarState ::Instance ()); for (int ElapsedDays = 0 ; ElapsedDays < 30 ; ElapsedDays +=4 ) { pW ->SetElapsedDays (ElapsedDays ); pW ->RunWar (); } SAFE_RELASE_POINTER (pW ); }
5.运行结果:
Now The War Is On Prephase State, Prephase dota heroes become the protagonist, War is coming to begin[time = 0]
Now The War Is On Prephase State, Prephase dota heroes become the protagonist, War is coming to begin[time = 4]
Now The War Is On Prephase State, Prephase dota heroes become the protagonist, War is coming to begin[time = 8]
Now The War Is On Metaphase State, Anaphase and Prephase dota heroes both become the protagonist, War is on hot[time = 12]
Now The War Is On Metaphase State, Anaphase and Prephase dota heroes both become the protagonist, War is on hot[time = 16]
Now The War Is On Metaphase State, Anaphase and Prephase dota heroes both become the protagonist, War is on hot[time = 20]
Now The War Is On Anaphase State, Anaphase dota heroes become the protagonist, War is coming to end[time = 24]
Now The War Is On Anaphase State, Anaphase dota heroes become the protagonist, War is coming to end[time = 28]