設計模式 c++版(19)—— 狀態模式
阿新 • • 發佈:2019-02-16
定義:
當一個物件內在狀態改變時允許其改變行為,這個物件看起來像改變了其類。(狀態的變更引起了行為的變更,從外部看起來好像這個物件對應的類發生了改變一樣)
示例一:狀態模式(通用版)
1. 類圖 26-5
2. 類圖說明
State 抽象狀態角色
介面或抽象類,負責物件狀態定義,並且封裝環境角色以實現狀態切換。
ConcreteState 具體狀態角色
每一個具體狀態必須完成兩個職責:本狀態的行為管理以及趨向狀態處理,即本狀態要做的事情,以及本狀態如何過度到其他狀態。
Context 環境角色
定義客戶端需要的介面,並且負責具體狀態的切換。
3. 程式碼清單
#include <QCoreApplication> #include <QDebug> class Context; //抽象環境角色 class State { public: void setContext(Context &context) { this->m_context = &context; } virtual void handle1() = 0; virtual void handle2() = 0; protected: Context *m_context; }; //具體環境角色 class Context { public: State* getCurrentState() { return this->m_curState; } void setCurrentState(State* state) { this->m_curState = state; this->m_curState->setContext(*this); } void handle1() { this->m_curState->handle1(); } void handle2() { this->m_curState->handle2(); } public: static State* m_state1; static State* m_state2; private: State* m_curState; }; //環境角色 class State1:public State { public: virtual void handle1() { qDebug() << "this is handle1"; } virtual void handle2() { this->m_context->setCurrentState(this->m_context->m_state2); this->m_context->handle2(); } }; class State2:public State { public: virtual void handle1() { this->m_context->setCurrentState(this->m_context->m_state1); this->m_context->handle1(); } virtual void handle2() { qDebug() << "this is handle2"; } }; State* Context::m_state1 = new State1(); State* Context::m_state2 = new State2(); int main() { Context context; State* state1 = new State1(); context.setCurrentState(state1); context.handle1(); context.handle2(); delete state1; return 0; }
示例二:電梯
1. 類圖 26-1
2. 分析特定狀態
- 敞門狀態。這時可以關門
- 閉門狀態。這時可以開門,停止,執行
- 執行狀態。這時只能停止
- 停止狀態。這時可以執行和開門
3. 電梯狀態和動作之間關係圖 26-2
4. 擴充套件狀態後的類圖 26-3
5. 擴充套件狀態後代碼
//電梯介面 class ILift { public: virtual void open() = 0; virtual void close() = 0; virtual void run() = 0; virtual void stop() = 0; virtual void setState(int state) = 0; public: static const int OPENING_STATE = 1; static const int CLOSING_STATE = 2; static const int RUNNING_STATE = 3; static const int STOPPING_STATE = 4; }; //電梯實現 class Lift:public ILift { public: virtual void 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; } } virtual void 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; } } virtual void 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; } } virtual void 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; } } virtual void setState(int state) { this->m_state = state; } private: void closeWithoutLogic() { qDebug() << "close"; } void openWithoutLogic() { qDebug() << "open"; } void runWithoutLogic() { qDebug() << "run"; } void stopWithoutLogic() { qDebug() << "stop"; } private: int m_state; }; int main() { ILift *lift = new Lift(); lift->setState(ILift::STOPPING_STATE); lift->open(); lift->close(); lift->run(); lift->stop(); delete lift; return 0; }
6. 存在的問題
電梯實現類Lift有點長。裡面使用了大量的switch...case,在業務複雜的情況下,程式會更長。
擴充套件性非常差。電梯還有通電和斷點兩個狀態,若要增加這兩個方法,原有4個方法都需要增加判斷條件
非常規狀態無法實現。
7. 使用狀態模式,類圖 26-4
8. 使用狀態模式,程式碼
/ ******************************************* 宣告 ********************************* /// class LiftState; //上下文類 宣告 class Context { public: Context(); LiftState* getLiftState(); void setLiftState(LiftState *liftState); void open(); void close(); void run(); void stop(); public: static LiftState *m_openningState; static LiftState *m_closingState; static LiftState *m_runningState; static LiftState *m_stoppingState; private: LiftState *m_liftState; }; //抽象電梯狀態 宣告 class LiftState { public: void setContext(Context &context); virtual void open() = 0; virtual void close() = 0; virtual void run() = 0; virtual void stop() = 0; protected: Context *m_context; }; //關閉狀態 宣告 class ClosingState:public LiftState { public: virtual void open(); virtual void close(); virtual void run(); virtual void stop(); }; //敞門狀態 宣告 class OpenningState:public LiftState { public: virtual void open(); virtual void close(); virtual void run(); virtual void stop(); }; //執行狀態 宣告 class RunningState:public LiftState { public: virtual void open(); virtual void close(); virtual void run(); virtual void stop(); }; //停止狀態 宣告 class StoppingState:public LiftState { public: virtual void open(); virtual void close(); virtual void run(); virtual void stop(); }; /// *********************************************** 定義 ************************************************* /// //上下文類 定義 LiftState* Context::m_openningState = new OpenningState(); LiftState* Context::m_closingState = new ClosingState(); LiftState* Context::m_runningState = new RunningState(); LiftState* Context::m_stoppingState = new StoppingState(); Context::Context() { this->setLiftState(this->m_closingState); } LiftState* Context::getLiftState() { return this->m_liftState; } void Context::setLiftState(LiftState* liftState) { this->m_liftState = liftState; this->m_liftState->setContext(*this); } void Context::open() { this->m_liftState->open(); } void Context::close() { this->m_liftState->close(); } void Context::run() { this->m_liftState->run(); } void Context::stop() { this->m_liftState->stop(); } //抽象電梯狀態 定義 void LiftState::setContext(Context &context) { this->m_context = &context; } //關閉狀態 定義 void ClosingState::open() { this->m_context->setLiftState(m_context->m_openningState); this->m_context->getLiftState()->open(); } void ClosingState::close() { qDebug() << "close"; } void ClosingState::run() { this->m_context->setLiftState(m_context->m_runningState); this->m_context->getLiftState()->run(); } void ClosingState::stop() { this->m_context->setLiftState(m_context->m_stoppingState); this->m_context->getLiftState()->stop(); } //敞門狀態 定義 void OpenningState::open() { qDebug() << "open"; } void OpenningState::close() { this->m_context->setLiftState(m_context->m_closingState); this->m_context->getLiftState()->close(); } void OpenningState::run(){} void OpenningState::stop(){} //執行狀態 定義 void RunningState::open(){} void RunningState::close(){} void RunningState::run() { qDebug() << "run"; } void RunningState::stop() { this->m_context->setLiftState(m_context->m_stoppingState); this->m_context->getLiftState()->stop(); } //停止狀態 定義 void StoppingState::open() { this->m_context->setLiftState(m_context->m_openningState); this->m_context->getLiftState()->open(); } void StoppingState::close(){} void StoppingState::run() { this->m_context->setLiftState(m_context->m_runningState); this->m_context->getLiftState()->run(); } void StoppingState::stop() { qDebug() << "stop"; } int main() { Context context; context.open(); context.close(); context.run(); context.stop(); return 0; }
三、狀態模式的應用
1. 優點:
- 結構清晰。避免了過多的條件巢狀導致的程式的複雜性,提高可維護性。
- 遵循設計原則。很好地體現了開閉原則和單一職責原則,每個狀態都是一個子類,要增加狀態就要增加子類,需要修改狀態,只修改一個子類就好
- 封裝性好。狀態變換放置到類的內部實現,外部的呼叫不用知道類內部如何實現狀態和行為的變換。
2. 缺點:
子類會太多,也就是類膨脹。如果完全使用狀態模式就會有太多的子類,不好管理,需要在專案中自己衡量。有很多方式可以解決這個狀態問題,如在資料庫中建立一個狀態表,然後根據狀態執行響應的操作。
3. 使用場景:
- 行為隨狀態改變而改變的場景。例:許可權設計,人員的狀態不同即使執行相同的行為結果也會不同,在這種情況下需要考慮使用狀態模式。
- 條件、分支判斷語句的替代者。在程式中大量使用switch或if潘丹語句會導致程式結構不清晰,邏輯混亂。
4. 注意事項:
狀態模式適用於當前某個物件在它的狀態發生改變時,它的行為也隨著發生比較大的變化,也就是說在行為受狀態約束的情況下可以使用狀態模式,而且使用時物件的狀態最好不要超過5個。
參考文獻《秦小波. 設計模式之禪》(第2版) (華章原創精品) 機械工業出版社