目標場景
假設我們現在有一個設備控制程序,上面有運行
、暫停
和停止
三個按鈕,並且我們已實現了對應的邏輯控制代碼,如下圖:
void on_pushButton_Run_clicked()
{
setState("啓動運行");
run();
setState("運行中");
}
void on_pushButton_Pause_clicked()
{
setState("正在暫停...");
pause();
setState("已暫停");
}
void on_pushButton_Stop_clicked()
{
setState("正在停止...");
stop();
setState("已停止");
}
由於我們沒有作狀態的限制,如在運行狀態的時候我們可以再次點擊運行按鈕,這時候是不合理的
我們當然可以在按鈕點擊後將不能點擊的按鈕禁用來達到我們的目標。假設我們的規則如下:
- 在初始狀態下只能點擊運行按鈕。
- 運行狀態下可以點擊暫停和停止按鈕。
- 暫停狀態下可以點擊運行和停止按鈕。
- 停止狀態可以點運行按鈕。
public:
MainWindow(): QMainWindow(nullptr)
{
ui.setupUi(this);
ui.pushButton_Run->setEnabled(true);
ui.pushButton_Pause->setEnabled(false);
ui.pushButton_Stop->setEnabled(false);
}
private slots:
void on_pushButton_Run_clicked()
{
setState("啓動運行");
run();
setState("運行中");
ui.pushButton_Run->setEnabled(false);
ui.pushButton_Pause->setEnabled(true);
ui.pushButton_Stop->setEnabled(true);
}
void on_pushButton_Pause_clicked()
{
setState("正在暫停...");
pause();
setState("已暫停");
ui.pushButton_Run->setEnabled(true);
ui.pushButton_Pause->setEnabled(false);
ui.pushButton_Stop->setEnabled(true);
}
void on_pushButton_Stop_clicked()
{
setState("正在停止...");
stop();
setState("已停止");
ui.pushButton_Run->setEnabled(true);
ui.pushButton_Pause->setEnabled(false);
ui.pushButton_Stop->setEnabled(false);
}
OK,功能全部完美實現!!!
狀態圖
好吧,如果你看到了這裏,那你肯定覺得上面的實現不完美!對於上面的場景,我們可以用狀態圖來直觀地表示,那麼什麼是狀態圖呢?客官請看,下面這個就是狀態圖:
其中框框表示狀態(小圓圈框框一般用來表示初始狀態和終止狀態),箭頭表示觸發條件。上面的狀態圖就可以很直觀地表示了我們上面描述的規則了。直觀的表示方法還有狀態表,客官們感興趣請個人自家查詢相關資料。
原狀態\目標狀態 | 初始狀態 | 運行 | 暫停 | 停止 |
---|---|---|---|---|
初始狀態 | - | 運行 | - | - |
運行 | - | - | 暫停 | 停止 |
暫停 | - | 運行 | - | 停止 |
停止 | - | 運行 | - | - |
枚舉實現
上面的代碼不完美的首要一點是狀態相關代碼和UI的代碼耦合度太高,並且狀態沒有任何內部的數據表示。我們改用枚舉的方式實現:
enum State {
NoneState,
RunState,
PauseState,
StopState
};
public:
MainWindow(): QMainWindow(nullptr)
{
ui.setupUi(this);
setState(NoneState);
}
private slots:
void on_pushButton_Run_clicked()
{
setStateText("啓動運行");
run();
setState(RunState);
}
void on_pushButton_Pause_clicked()
{
setStateText("正在暫停...");
pause();
setState(PauseState);
}
void on_pushButton_Stop_clicked()
{
setStateText("正在停止...");
stop();
setState(StopState);
}
void setState(State state) {
m_state = state;
switch (m_state)
{
case NoneState:
ui.pushButton_Run->setEnabled(true);
ui.pushButton_Pause->setEnabled(false);
ui.pushButton_Stop->setEnabled(false);
setStateText("初始狀態");
break;
case RunState:
ui.pushButton_Run->setEnabled(false);
ui.pushButton_Pause->setEnabled(true);
ui.pushButton_Stop->setEnabled(true);
setStateText("運行中");
break;
case PauseState:
ui.pushButton_Run->setEnabled(true);
ui.pushButton_Pause->setEnabled(false);
ui.pushButton_Stop->setEnabled(true);
setStateText("已暫停");
break;
case StopState:
ui.pushButton_Run->setEnabled(true);
ui.pushButton_Pause->setEnabled(false);
ui.pushButton_Stop->setEnabled(false);
setStateText("已停止");
break;
}
}
private:
State m_state;
這樣調整後我們的界面更新代碼全部在setState
函數中,對於狀態比較少且簡單情況,此方案是最簡單和高效的,但是一旦狀態數量和轉換條件增加,setState
函數將會變得很大且複雜。
狀態機模式
我們可以將狀態枚舉抽象成狀態類,然後在各自的狀態類中實現邏輯,這就是狀態機模式:
class StateBase;
class Core
{
public:
Core() {}
// UI
virtual QPushButton* runButton() = 0;
virtual QPushButton* pauseButton() = 0;
virtual QPushButton* stopButton() = 0;
virtual void setText(const QString& text) = 0;
// Device
virtual void run() = 0;
virtual void pause() = 0;
virtual void stop() = 0;
void setState(StateBase* state) { m_currentState = state; }
StateBase* noneState;
StateBase* runState;
StateBase* pauseState;
StateBase* stopState;
StateBase* m_currentState;
};
class StateBase : public QObject
{
Q_OBJECT
public:
StateBase(Core* core,QObject* parent):QObject(parent), m_core(core){}
virtual void run() {}
virtual void pause() {}
virtual void stop(){}
protected:
Core* m_core;
};
class NoneState : public StateBase
{
public:
NoneState(Core* core, QObject* parent):StateBase(core,parent){}
virtual void run() override
{
m_core->setText("啓動運行");
m_core->run();
m_core->runButton()->setEnabled(false);
m_core->pauseButton()->setEnabled(true);
m_core->stopButton()->setEnabled(true);
m_core->setText("運行中");
m_core->setState(m_core->runState);
}
};
class RunState : public StateBase
{
public:
RunState(Core* core, QObject* parent) :StateBase(core, parent) {}
virtual void pause() override
{
m_core->setText("準備暫停");
m_core->pause();
m_core->runButton()->setEnabled(true);
m_core->pauseButton()->setEnabled(false);
m_core->stopButton()->setEnabled(true);
m_core->setText("已暫停");
m_core->setState(m_core->pauseState);
}
virtual void stop() override
{
m_core->setText("準備停止");
m_core->stop();
m_core->runButton()->setEnabled(false);
m_core->pauseButton()->setEnabled(false);
m_core->stopButton()->setEnabled(true);
m_core->setText("已停止");
m_core->setState(m_core->stopState);
}
};
class PauseState : public StateBase
{
public:
PauseState(Core* core, QObject* parent) :StateBase(core, parent) {}
virtual void run() override
{
m_core->setText("啓動運行");
m_core->run();
m_core->runButton()->setEnabled(false);
m_core->pauseButton()->setEnabled(true);
m_core->stopButton()->setEnabled(true);
m_core->setText("運行中");
m_core->setState(m_core->runState);
}
virtual void stop() override
{
m_core->setText("準備停止");
m_core->stop();
m_core->runButton()->setEnabled(false);
m_core->pauseButton()->setEnabled(false);
m_core->stopButton()->setEnabled(true);
m_core->setText("已停止");
m_core->setState(m_core->stopState);
}
};
class StopState : public StateBase
{
public:
StopState(Core* core, QObject* parent) :StateBase(core, parent) {}
virtual void run() override
{
m_core->setText("啓動運行");
m_core->run();
m_core->runButton()->setEnabled(false);
m_core->pauseButton()->setEnabled(true);
m_core->stopButton()->setEnabled(true);
m_core->setText("運行中");
m_core->setState(m_core->runState);
}
virtual void pause() override
{
m_core->setText("準備暫停");
m_core->pause();
m_core->runButton()->setEnabled(true);
m_core->pauseButton()->setEnabled(false);
m_core->stopButton()->setEnabled(true);
m_core->setText("已暫停");
m_core->setState(m_core->pauseState);
}
};
class MainWindow : public QMainWindow, public Core
{
Q_OBJECT
public:
MainWindow(): QMainWindow(nullptr)
{
ui.setupUi(this);
noneState = new NoneState(this, this);
runState = new RunState(this, this);
pauseState = new PauseState(this, this);
stopState = new StopState(this, this);
setState(noneState);
ui.pushButton_Pause->setEnabled(false);
ui.pushButton_Stop->setEnabled(false);
}
virtual QPushButton* runButton() override { return ui.pushButton_Run; }
virtual QPushButton* pauseButton() override { return ui.pushButton_Pause; }
virtual QPushButton* stopButton() override { return ui.pushButton_Stop; }
virtual void setText(const QString& text) override { setStateText(text); }
virtual void run() override { _run(); }
virtual void pause() override { _pause(); }
virtual void stop() override { _stop(); }
private slots:
void on_pushButton_Run_clicked()
{
m_currentState->run();
}
void on_pushButton_Pause_clicked()
{
m_currentState->pause();
}
void on_pushButton_Stop_clicked()
{
m_currentState->stop();
}
注1:工程項目中一定要分離界面和業務邏輯,此處爲了方便演示,將界面和業務邏輯全部放入了Core
類中。
注2:正統的狀態機還有一個狀態機類StateMachine
,此處也偷懶了,相關的功能也放在了Core
類。對狀態機模式感興趣的同學請查閱相關資料。
採用狀態機模式後,得到的好處有:
- 狀態相互之間不再是網狀結構: 狀態只需要實現相關的條件切換即可,如
NoneState
只需實現run
條件。 - 增加修改狀態很方便: 增加狀態只需要新子類化一個狀態類或修改狀態類,然後編寫相關的條件邏輯,不影響現有狀態邏輯。
- 代碼整潔乾淨: 將轉換邏輯交給了狀態類,邏輯清洗且更加合理,狀態類的邏輯和狀態圖能夠很好地對應。
- 代碼更加健壯: 即使我們在狀態切換時忘記了禁用按鈕,用戶多次點擊也不會出現任何問題。
狀態機框架
狀態機模式雖然滿足了我們的需求(常規的書籍上面也只會講到這裏),但是存在2個問題(至少我覺得是):
- 模式與我們的工程耦合度太高,換一個項目需重新實現一套狀態機模式。
- 存在重複代碼,如
PauseState
和RunState
的stop
函數(雖然可以提升到基類中實現,但是基類會變得異常龐大)。
現在讓我們來進一步探究,首先再次回顧一下我的狀態圖:
根據狀態圖,我們可以抽象出2個類:狀態類和轉換條件。爲了維護狀態之間的切換,我們還需要一個總體的協調類,我們分別命名爲XState
、XStateTransition
和XStateMachine
:
class XState { };
class XStateTransition{};
class XStateMachine{};
對於上面的狀態圖,我們期望用戶可以像下面這樣寫代碼:
noneState->addTransition(run,runState);
runState->addTransition(pause,pauseState);
runState->addTransition(stop,stopState);
pauseState->addTransition(run,runState);
pauseState->addTransition(stop,stopState);
stopState->addTransition(run,runState);
這樣代碼是不是超級清晰、簡單、直觀、好看、美麗、可愛、善良呢?期望還是要有的,萬一我們可以實現呢!!!
首先我們需要保存所有轉換條件和目標狀態,以便在合適的時機來切換:
#include <unordered_map>
class XStateTransition;
class XStateMachine;
class XState
{
public:
XState(XStateMachine* stateMachine):m_stateMachine(stateMachine){}
void addTransition(XStateTransition* transition, XState* targetState)
{
m_stateMap[transition] = targetState;
}
bool trigger(XStateTransition* transition);
private:
XStateMachine* m_stateMachine;
std::unordered_map<XStateTransition*, XState*> m_stateMap;
};
class XStateMachine
{
public:
void changeState(XState* state)
{
m_currentState = state;
}
bool trigger(XStateTransition* transition)
{
return m_currentState->trigger(transition);
}
private:
XState* m_currentState;
};
inline bool XState::trigger(XStateTransition* transition)
{
auto itr = m_stateMap.find(transition);
if (itr != m_stateMap.end()) {
m_stateMachine->changeState(itr->second);
return true;
}
return false;
}
class XStateTransition
{
public:
XStateTransition(XStateMachine* stateMachie):m_stateMachine(stateMachie){}
void trigger() { m_stateMachine->trigger(this); }
private:
XStateMachine* m_stateMachine;
};
調整我們的項目代碼,之前的狀態機模式代碼可以全部刪除了:
class MainWindow : public QMainWindow, public Core
{
Q_OBJECT
public:
MainWindow(): QMainWindow(nullptr)
{
ui.setupUi(this);
m_stateMachine = new XStateMachine();
m_run = new XStateTransition(m_stateMachine);
m_pause = new XStateTransition(m_stateMachine);
m_stop = new XStateTransition(m_stateMachine);
m_noneState = new XState(m_stateMachine);
m_runState = new XState(m_stateMachine);
m_pauseState = new XState(m_stateMachine);
m_stopState = new XState(m_stateMachine);
m_noneState->addTransition(m_run, m_runState);
m_runState->addTransition(m_pause, m_pauseState);
m_runState->addTransition(m_stop, m_stopState);
m_pauseState->addTransition(m_run, m_runState);
m_pauseState->addTransition(m_stop, m_stopState);
m_stopState->addTransition(m_run, m_runState);
m_stateMachine->changeState(m_noneState);
ui.pushButton_Pause->setEnabled(false);
ui.pushButton_Stop->setEnabled(false);
}
virtual QPushButton* runButton() override { return ui.pushButton_Run; }
virtual QPushButton* pauseButton() override { return ui.pushButton_Pause; }
virtual QPushButton* stopButton() override { return ui.pushButton_Stop; }
virtual void setText(const QString& text) override { setStateText(text); }
virtual void run() override { _run(); }
virtual void pause() override { _pause(); }
virtual void stop() override { _stop(); }
private slots:
void on_pushButton_Run_clicked()
{
m_run->trigger();
}
void on_pushButton_Pause_clicked()
{
m_pause->trigger();
}
void on_pushButton_Stop_clicked()
{
m_stop->trigger();
}
private:
XStateMachine* m_stateMachine;
XState* m_noneState ;
XState* m_runState ;
XState* m_pauseState ;
XState* m_stopState ;
XStateTransition* m_run ;
XStateTransition* m_pause ;
XStateTransition* m_stop ;
目前狀態機框架應該時正常工作的,但是界面並沒有變化,因爲我們沒有進行任何界面處理。我們在XStateTransition
中定義一個虛函數用於狀態切換時更新信息:
class XStateTransition
{
public:
XStateTransition(XStateMachine* stateMachie):m_stateMachine(stateMachie){}
void trigger()
{
if (m_stateMachine->trigger(this))
{
triggerEvent();
}
}
protected:
virtual void triggerEvent() {}
private:
XStateMachine* m_stateMachine;
};
子類化狀態切換類並實現具體的功能的功能:
class RunTransition : public XStateTransition
{
public:
RunTransition(XStateMachine* stateMachine, Core* core):XStateTransition(stateMachine),m_core(core){}
protected:
virtual void triggerEvent() override
{
m_core->setText("啓動運行");
m_core->run();
m_core->runButton()->setEnabled(false);
m_core->pauseButton()->setEnabled(true);
m_core->stopButton()->setEnabled(true);
m_core->setText("運行中");
}
private:
Core* m_core;
};
class PauseTransition : public XStateTransition
{
public:
PauseTransition(XStateMachine* stateMachine, Core* core) :XStateTransition(stateMachine), m_core(core) {}
protected:
virtual void triggerEvent() override
{
m_core->setText("準備暫停");
m_core->pause();
m_core->runButton()->setEnabled(true);
m_core->pauseButton()->setEnabled(false);
m_core->stopButton()->setEnabled(true);
m_core->setText("已暫停");
}
private:
Core* m_core;
};
class StopTransition : public XStateTransition
{
public:
StopTransition(XStateMachine* stateMachine, Core* core) :XStateTransition(stateMachine), m_core(core) {}
protected:
virtual void triggerEvent() override
{
m_core->setText("準備停止");
m_core->stop();
m_core->runButton()->setEnabled(true);
m_core->pauseButton()->setEnabled(false);
m_core->stopButton()->setEnabled(false);
m_core->setText("已停止");
}
private:
Core* m_core;
};
修改初始化代碼:
m_run = new RunTransition(m_stateMachine,this);
m_pause = new PauseTransition(m_stateMachine,this);
m_stop = new StopTransition(m_stateMachine,this);
到目前爲止,我們成功解決狀態機模式遺留的幾個問題,想想都有些激動呢!
等等!!! 我們的按鈕是否可用是否跟轉換條件有關,例如,如果可以切換到運行狀態纔可以點擊運行按鈕,跟條件切換的思路一樣,我們同樣可以通過虛函數來達到此目的:
class XState
{
public:
...
bool haveTransition(XStateTransition* transition)const
{
return m_stateMap.find(transition) != m_stateMap.end();
}
...
};
class XStateMachine
{
public:
void changeState(XState* state)
{
if(m_currentState!=state){
m_currentState = state;
changeStateEvent(m_currentState);
}
}
bool trigger(XStateTransition* transition)
{
return m_currentState->trigger(transition);
}
protected:
virtual void changeStateEvent(XState* state) {}
private:
XState* m_currentState;
};
實現對於的子類並修改工程代碼,刪除狀態轉換子類的按鈕相關代碼:
class StateMachine : public XStateMachine
{
public:
StateMachine(Core* core) :m_core(core)
{
m_run = new RunTransition(this, m_core);
m_pause = new PauseTransition(this, m_core);
m_stop = new StopTransition(this, m_core);
m_noneState = new XState(this);
m_runState = new XState(this);
m_pauseState = new XState(this);
m_stopState = new XState(this);
m_noneState->addTransition(m_run, m_runState);
m_runState->addTransition(m_pause, m_pauseState);
m_runState->addTransition(m_stop, m_stopState);
m_pauseState->addTransition(m_run, m_runState);
m_pauseState->addTransition(m_stop, m_stopState);
m_stopState->addTransition(m_run, m_runState);
changeState(m_noneState);
}
void run() { m_run->trigger(); }
void pause() { m_pause->trigger(); }
void stop() { m_stop->trigger(); }
protected:
virtual void changeStateEvent(XState* state)override
{
m_core->runButton()->setEnabled(state->haveTransition(m_run));
m_core->pauseButton()->setEnabled(state->haveTransition(m_pause));
m_core->stopButton()->setEnabled(state->haveTransition(m_stop));
}
private:
Core* m_core;
XState* m_noneState;
XState* m_runState;
XState* m_pauseState;
XState* m_stopState;
XStateTransition* m_run;
XStateTransition* m_pause;
XStateTransition* m_stop;
};
class MainWindow : public QMainWindow, public Core
{
Q_OBJECT
public:
MainWindow(): QMainWindow(nullptr)
{
ui.setupUi(this);
m_stateMachine = new StateMachine(this);
}
virtual QPushButton* runButton() override { return ui.pushButton_Run; }
virtual QPushButton* pauseButton() override { return ui.pushButton_Pause; }
virtual QPushButton* stopButton() override { return ui.pushButton_Stop; }
virtual void setText(const QString& text) override { setStateText(text); }
virtual void run() override { _run(); }
virtual void pause() override { _pause(); }
virtual void stop() override { _stop(); }
private slots:
void on_pushButton_Run_clicked()
{
m_stateMachine->run();
}
void on_pushButton_Pause_clicked()
{
m_stateMachine->pause();
}
void on_pushButton_Stop_clicked()
{
m_stateMachine->stop();
}
private:
StateMachine* m_stateMachine;
至此,我們狀態機框架已圓滿完成!!!
。。。好吧,看來你還沒走,那我們繼續。。。
子狀態
假設我們的狀態圖變成如下形式:
也就是設備啓動的時候處於未就緒狀態,自檢成功後處於就緒狀態。就緒、運行和暫停都屬於聯機狀態,在任意聯機狀態下發生故障回到未就緒狀態。其中就緒、運行和暫停就是聯機狀態的子狀態。當然可以不用子狀態來表示,爲每個聯機狀態的子狀態添加轉換條件是可以達到目的的,但是這不是我們風格,對吧?
爲了我們的狀態機框架能夠支持子狀態,我們需要調整我們的XState
,實現上面我們可以選擇記錄所有的孩子狀態、和可以保存父親狀態。此處我選擇保存父親狀態,另外XState
還需要保存當前的子狀態,XStateMachine
感覺很像一個有孩子狀態的狀態,所以我們就讓XStateMachine
直接繼承自XState
,調整代碼如下:
class XStateTransition;
class XState
{
public:
XState(XState* parent):m_parent(parent),m_currentState(nullptr){}
virtual ~XState(){}
void addTransition(XStateTransition* transition, XState* targetState)
{
m_stateMap[transition] = targetState;
}
bool haveTransition(XStateTransition* transition)const
{
auto itr = m_stateMap.find(transition);
if (itr != m_stateMap.end()) {
return true;
}
if (m_currentState) {
return m_currentState->haveTransition(transition);
}
return false;
}
XState* currentState(bool recursively=false)const
{
if (recursively) {
if (m_currentState) {
if (m_currentState->m_currentState) {
return m_currentState->currentState(true);
}
else {
return m_currentState;
}
}
else {
return nullptr;
}
}
else {
return m_currentState;
}
}
protected:
virtual void changeStateEvent() {}
virtual void enterEvent() {}
virtual void exitEvent() {}
void changeState(XState* state)
{
if (m_currentState != state) {
if (m_currentState) {
m_currentState->exit();
}
m_currentState = findChild(state);
if (m_currentState) {
m_currentState->enter();
}
// 子狀態繼續切換狀態
if (m_currentState != state) {
m_currentState->changeState(state);
}
changeStateEvent();
if (m_parent) {
m_parent->changeStateEvent();
}
}
}
void trigger(XStateTransition* transition)
{
// Trigger current;
auto itr = m_stateMap.find(transition);
if (itr != m_stateMap.end()) {
m_parent->changeState(itr->second);
return;
}
// Trigger child
if (m_currentState) {
m_currentState->trigger(transition);
return;
}
Q_ASSERT(false);
}
void enter() { enterEvent(); }
void exit() { exitEvent(); }
XState* findChild(XState* descendant)const
{
if (descendant == nullptr) {
return nullptr;
}
if (descendant->m_parent == this)
{
return descendant;
}
if (descendant->m_parent == nullptr)
{
return nullptr;
}
return findChild(descendant->m_parent);
}
private:
XState* m_parent;
XState* m_currentState;
std::unordered_map<XStateTransition*, XState*> m_stateMap;
};
class XStateMachine : public XState
{
public:
XStateMachine() :XState(nullptr) {}
virtual ~XStateMachine(){}
void trigger(XStateTransition* transition) { XState::trigger(transition); }
};
class XStateTransition
{
public:
XStateTransition(XStateMachine* stateMachie):m_stateMachine(stateMachie){}
virtual ~XStateTransition() {}
void trigger()
{
if (m_stateMachine->haveTransition(this) && triggerEvent()) {
m_stateMachine->trigger(this);
}
}
protected:
virtual bool triggerEvent() { return true; }
private:
XStateMachine* m_stateMachine;
};
class Core
{
public:
Core() {}
// UI
virtual QPushButton* runButton() = 0;
virtual QPushButton* pauseButton() = 0;
virtual QPushButton* stopButton() = 0;
virtual QPushButton* checkButton() = 0;
virtual QPushButton* errButton() = 0;
virtual void setText(const QString& text) = 0;
// Device
virtual void run() = 0;
virtual void pause() = 0;
virtual void stop() = 0;
virtual bool check() = 0;
virtual void err() = 0;
};
class CheckOkTransition : public XStateTransition
{
public:
CheckOkTransition(XStateMachine* stateMachine, Core* core) :XStateTransition(stateMachine), m_core(core) {}
protected:
virtual bool triggerEvent() override
{
m_core->setText("自檢中...");
if (m_core->check()) {
return true;
}
else {
m_core->setText("自檢失敗");
return false;
}
}
private:
Core* m_core;
};
class ErrTransition : public XStateTransition
{
public:
ErrTransition(XStateMachine* stateMachine, Core* core) :XStateTransition(stateMachine), m_core(core) {}
protected:
virtual bool triggerEvent() override
{
m_core->setText("發生故障");
return true;
}
private:
Core* m_core;
};
class RunTransition : public XStateTransition
{
public:
RunTransition(XStateMachine* stateMachine, Core* core):XStateTransition(stateMachine),m_core(core){}
protected:
virtual bool triggerEvent() override
{
m_core->setText("啓動運行");
m_core->run();
return true;
}
private:
Core* m_core;
};
class PauseTransition : public XStateTransition
{
public:
PauseTransition(XStateMachine* stateMachine, Core* core) :XStateTransition(stateMachine), m_core(core) {}
protected:
virtual bool triggerEvent() override
{
m_core->setText("準備暫停");
m_core->pause();
return true;
}
private:
Core* m_core;
};
class StopTransition : public XStateTransition
{
public:
StopTransition(XStateMachine* stateMachine, Core* core) :XStateTransition(stateMachine), m_core(core) {}
protected:
virtual bool triggerEvent() override
{
m_core->setText("準備停止");
m_core->stop();
return true;
}
private:
Core* m_core;
};
class State : public XState
{
public:
State(XState* parent, const QString& text, Core* core):XState(parent),m_text(text),m_core(core){}
QString text() {return m_text; }
protected:
virtual void enterEvent()override
{
m_core->setText(m_text);
}
private:
Core* m_core;
QString m_text;
};
class StateMachine : public XStateMachine
{
public:
StateMachine(Core* core) :m_core(core)
{
m_check = new CheckOkTransition(this, m_core);
m_err = new ErrTransition(this, m_core);
m_run = new RunTransition(this, m_core);
m_pause = new PauseTransition(this, m_core);
m_stop = new StopTransition(this, m_core);
m_offlineState = new State(this,"脫機",m_core);
m_onlineState = new State(this,"聯機", m_core);
m_noneState = new State(m_onlineState, "就緒", m_core);
m_runState = new State(m_onlineState, "運行", m_core);
m_pauseState = new State(m_onlineState, "已暫停", m_core);
m_offlineState->addTransition(m_check, m_noneState);
m_noneState->addTransition(m_run, m_runState);
m_runState->addTransition(m_pause, m_pauseState);
m_runState->addTransition(m_stop, m_noneState);
m_pauseState->addTransition(m_run, m_runState);
m_pauseState->addTransition(m_stop, m_noneState);
m_onlineState->addTransition(m_err, m_offlineState);
changeState(m_offlineState);
}
void check() { m_check->trigger(); }
void run() { m_run->trigger(); }
void pause() { m_pause->trigger(); }
void stop() { m_stop->trigger(); }
void err() { m_err->trigger(); }
protected:
virtual void changeStateEvent()override
{
auto state = currentState(true);
m_core->checkButton()->setEnabled(state->haveTransition(m_check));
m_core->runButton()->setEnabled(state->haveTransition(m_run));
m_core->pauseButton()->setEnabled(state->haveTransition(m_pause));
m_core->stopButton()->setEnabled(state->haveTransition(m_stop));
}
private:
Core* m_core;
XState* m_offlineState;
XState* m_onlineState;
XState* m_noneState;
XState* m_runState;
XState* m_pauseState;
XStateTransition* m_check;
XStateTransition* m_err;
XStateTransition* m_run;
XStateTransition* m_pause;
XStateTransition* m_stop;
};
class MainWindow : public QMainWindow, public Core
{
Q_OBJECT
public:
MainWindow(): QMainWindow(nullptr)
{
ui.setupUi(this);
m_stateMachine = new StateMachine(this);
}
virtual QPushButton* runButton() override { return ui.pushButton_Run; }
virtual QPushButton* pauseButton() override { return ui.pushButton_Pause; }
virtual QPushButton* stopButton() override { return ui.pushButton_Stop; }
virtual QPushButton* checkButton()override { return ui.pushButton_Check; }
virtual QPushButton* errButton() override { return ui.pushButton_Err; }
virtual void setText(const QString& text) override { setStateText(text); }
virtual void run() override { _run(); }
virtual void pause() override { _pause(); }
virtual void stop() override { _stop(); }
virtual bool check() override { sleep(1000); return QTime::currentTime().second() % 2 == 0; }
virtual void err() override {}
private slots:
void on_pushButton_Run_clicked()
{
m_stateMachine->run();
}
void on_pushButton_Pause_clicked()
{
m_stateMachine->pause();
}
void on_pushButton_Stop_clicked()
{
m_stateMachine->stop();
}
void on_pushButton_Check_clicked()
{
m_stateMachine->check();
}
void on_pushButton_Err_clicked()
{
m_stateMachine->err();
}
private:
StateMachine* m_stateMachine;
最後的倔強
如果我們有多個條件轉換到聯機狀態,並且我們希望聯機狀態的默認狀態不是就緒狀態,那麼上面的框架代碼有幾個條件轉換到就緒狀態就需要調整幾處代碼,我們可以增加一個初始狀態來解決,並在狀態進入時自動切換:
class XState
{
public:
XState(XState* parent):m_parent(parent),m_currentState(nullptr),m_initialState(nullptr){}
...
void enter()
{
enterEvent();
if (m_initialState) {
changeState(m_initialState);
}
}
private:
...
XState* m_initialState;
};
淺出
Qt已提供了狀態機框架(請參見Qt助手的The State Machine Framework
相關章節),用法基本和上面的一致,功能更全更強大,直接拿來用就好了!
狀態機框架完整代碼
#ifndef _X_STATE_MACHINE_HPP
#define _X_STATE_MACHINE_HPP
#include <unordered_map>
#include <cassert>
class XStateTransition;
class XState
{
public:
XState(XState* parent):m_parent(parent),m_currentState(nullptr),m_initialState(nullptr){}
virtual ~XState(){}
void addTransition(XStateTransition* transition, XState* targetState)
{
m_stateMap[transition] = targetState;
}
bool haveTransition(XStateTransition* transition)const
{
auto itr = m_stateMap.find(transition);
if (itr != m_stateMap.end()) {
return true;
}
if (m_currentState) {
return m_currentState->haveTransition(transition);
}
return false;
}
XState* currentState(bool recursively=false)const
{
if (recursively) {
if (m_currentState) {
if (m_currentState->m_currentState) {
return m_currentState->currentState(true);
}
else {
return m_currentState;
}
}
else {
return nullptr;
}
}
else {
return m_currentState;
}
}
void setInitialState(XState* state)
{
assert(m_initialState == nullptr&&state);
m_initialState = state;
}
protected:
virtual void changeStateEvent() {}
virtual void enterEvent() {}
virtual void exitEvent() {}
void changeState(XState* state)
{
if (m_currentState != state) {
if (m_currentState) {
m_currentState->exit();
}
m_currentState = findChild(state);
if (m_currentState) {
m_currentState->enter();
}
if (m_currentState != state) {
m_currentState->changeState(state);
}
changeStateEvent();
if (m_parent) {
m_parent->changeStateEvent();
}
}
}
void trigger(XStateTransition* transition)
{
// Trigger current;
auto itr = m_stateMap.find(transition);
if (itr != m_stateMap.end()) {
m_parent->changeState(itr->second);
return;
}
// Trigger child
if (m_currentState) {
m_currentState->trigger(transition);
return;
}
assert(false);
}
void enter()
{
enterEvent();
if (m_initialState) {
changeState(m_initialState);
}
}
void exit() { exitEvent(); }
XState* findChild(XState* descendant)const
{
if (descendant == nullptr) {
return nullptr;
}
if (descendant->m_parent == this)
{
return descendant;
}
if (descendant->m_parent == nullptr)
{
return nullptr;
}
return findChild(descendant->m_parent);
}
private:
XState* m_parent;
XState* m_currentState;
XState* m_initialState;
std::unordered_map<XStateTransition*, XState*> m_stateMap;
};
class XStateMachine : public XState
{
public:
XStateMachine() :XState(nullptr) {}
virtual ~XStateMachine(){}
void trigger(XStateTransition* transition) { XState::trigger(transition); }
void setInitialState(XState* state)
{
XState::setInitialState(state);
changeState(state);
}
};
class XStateTransition
{
public:
XStateTransition(XStateMachine* stateMachie):m_stateMachine(stateMachie){}
virtual ~XStateTransition() {}
void trigger()
{
if (m_stateMachine->haveTransition(this) && triggerEvent()) {
m_stateMachine->trigger(this);
}
}
protected:
virtual bool triggerEvent() { return true; }
private:
XStateMachine* m_stateMachine;
};
#ifdef X_UNIT_TEST
#include "XUnitTest.hpp"
class State : public XState
{
public:
State(const std::string& name, XState* state) :XState(state), m_name(name) {}
protected:
virtual void enterEvent()override
{
std::cout << "\t" << m_name << " ENTER" << std::endl;
}
virtual void exitEvent() override
{
std::cout << "\t" << m_name << " EXIT" << std::endl;
}
private:
std::string m_name;
};
class XStateMachineTest :public XUnitTest {
protected:
virtual void testEvent()
{
XStateMachine stateMachine;
State s1("s1", &stateMachine);
State sq("sq", &stateMachine);
State s11("s11", &s1);
State s12("s12", &s1);
State s13("s13", &s1);
s1.setInitialState(&s11);
stateMachine.setInitialState(&s1);
XStateTransition t1(&stateMachine);
s11.addTransition(&t1, &s12);
s12.addTransition(&t1, &s13);
s13.addTransition(&t1, &s11);
XStateTransition tq(&stateMachine);
s1.addTransition(&tq, &sq);
sq.addTransition(&tq, &s1);
X_OUTPUT(t1.trigger());
X_VERIFY(stateMachine.currentState(true) == &s12);
X_OUTPUT(t1.trigger());
X_VERIFY(stateMachine.currentState(true) == &s13);
X_OUTPUT(tq.trigger());
X_VERIFY(stateMachine.currentState(true) == &sq);
X_OUTPUT(t1.trigger());
X_VERIFY(stateMachine.currentState(true) == &sq);
X_OUTPUT(tq.trigger());
X_VERIFY(stateMachine.currentState(true) == &s11);
X_OUTPUT(t1.trigger());
X_VERIFY(stateMachine.currentState(true) == &s12);
X_OUTPUT(t1.trigger());
X_VERIFY(stateMachine.currentState(true) == &s13);
}
};
#endif // X_UNIT_TEST
#endif //_X_STATE_MACHINE_HPP
** 別翻了,這次真的沒有了!!! **