起因
那天下班吃完饭回宿舍,发现项目组长(住我隔壁屋)在打电话。我一开始没有在意,进屋收拾东西,谁知道面试完了之后组长喊了我一声,说刚电话面试完一个求职者,巴啦啦说了一堆,意思就是想考考我。
题目就是关于录音机各个按键切换,关于这个情景有什么想法。
说实话,我并没有一开始想到状态机,最后组长提醒我这算是很经典的状态机应用场景。然后我就开始思考,然后学习了一番。
示例说明
试下类似如下图效果:
简单实现了播放、暂停和关闭三种状态之间的转换。具体转换过程:播放->关闭,关闭->播放,播放->暂停,暂停->播放,关闭->播放。
如果觉得代码有点多,可以下载下来看,看文章里的也一样。只是现在csdn资源没法上传免费资源了,至少一个积分,下面的代码下载地址点这里。
方法一:简单利用Qt中的状态机框架
使用QStateMachine、QState、QEventTransition这三个类,分别对应状态机、状态和事件转换。主要函数:createState();
mainwindow.h:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QDialog>
#include <QPushButton>
#include <QState>
#include <QStateMachine>
#include <QTextEdit>
#include <QEventTransition>
#include <QVBoxLayout>
namespace Ui {
class MainWindow;
}
class MainWindow : public QDialog
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
void setUi(); //设置UI
void createState(); //创建状态机
private:
Ui::MainWindow *ui;
QTextEdit *m_textEdit; //状态显示
QPushButton *m_playBtn; //play按钮
QPushButton *m_stopBtn; //stop按钮
QPushButton *m_closeBtn; //close按钮
QStateMachine *m_stateMachine; //状态机
QState *m_playState; //play状态
QState *m_stopState; //stop状态
QState *m_closeState; //close状态
};
#endif // MAINWINDOW_H
MainWindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QDialog(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
setUi(); //设置UI
createState(); //创建状态机
}
MainWindow::~MainWindow()
{
delete ui;
}
//设置UI
void MainWindow::setUi()
{
m_textEdit = new QTextEdit(this);
m_textEdit->setEnabled(false);
m_playBtn = new QPushButton("Play",this);
m_stopBtn = new QPushButton("Stop",this);
m_closeBtn = new QPushButton("Close",this);
QHBoxLayout *btnLayout = new QHBoxLayout();
btnLayout->addWidget(m_playBtn);
btnLayout->addWidget(m_stopBtn);
btnLayout->addWidget(m_closeBtn);
QVBoxLayout *mainLayout = new QVBoxLayout();
mainLayout->addWidget(m_textEdit);
mainLayout->addLayout(btnLayout);
this->setLayout(mainLayout);
}
//创建状态机
void MainWindow::createState()
{
//初始化各个状态,以及状态触发后的动作(改变textEdit的显示)。
m_playState = new QState();
m_stopState = new QState();
m_closeState = new QState();
m_playState->assignProperty(m_textEdit,"plainText","Playing");
m_stopState->assignProperty(m_textEdit,"plainText","Stoped");
m_closeState->assignProperty(m_textEdit,"plainText","Closed");
//播放-》暂停 点击释放按钮之后转换状态
QEventTransition *play2StopTransition = new QEventTransition(m_stopBtn,QEvent::MouseButtonRelease);
m_playState->addTransition(play2StopTransition);
play2StopTransition->setTargetState(m_stopState);
//暂停-》播放
QEventTransition *stop2PlayTransition = new QEventTransition(m_playBtn,QEvent::MouseButtonRelease);
m_stopState->addTransition(stop2PlayTransition);
stop2PlayTransition->setTargetState(m_playState);
//暂停-》关闭
QEventTransition *stop2CloseTransition = new QEventTransition(m_closeBtn,QEvent::MouseButtonRelease);
m_stopState->addTransition(stop2CloseTransition);
stop2CloseTransition->setTargetState(m_closeState);
//播放-》关闭
QEventTransition *play2CloseTransition = new QEventTransition(m_closeBtn,QEvent::MouseButtonRelease);
m_playState->addTransition(play2CloseTransition);
play2CloseTransition->setTargetState(m_closeState);
//关闭-》播放
QEventTransition *close2PlayTransition = new QEventTransition(m_playBtn,QEvent::MouseButtonRelease);
m_closeState->addTransition(close2PlayTransition);
close2PlayTransition->setTargetState(m_playState);
//初始化状态机,将各个状态添加到状态机
m_stateMachine = new QStateMachine(this);
m_stateMachine->addState(m_playState);
m_stateMachine->addState(m_stopState);
m_stateMachine->addState(m_closeState);
//在状态机中设置初始状态为播放状态,并启动状态机
m_stateMachine->setInitialState(m_playState);
m_stateMachine->start();
}
方法二:状态模式
状态机算是状态模式的一个具体应用。
有一个状态基类State,类中有一个指向State的指针pNext,代表下一个状态,另外通过虚函数指定了要实现的动作(进行状态转换的动作)。具体的状态继承State这个基类,比如PlayState、StopState和CloseState,如果具体状态需要扩展,只需要继续增加具体类XXState。这些具体状态类实现了动作转换(虚函数),并在动作完成后通过基类指针pNext指向下一个状态。这些具体状态类都实现了单例,调用比较方便。
最后模拟了一个状态机类StateMachine,虽然这个类也继承了State但是目的是为了保持接口一致。
这个列子和方法一共用一个界面,所以下面只列出关键代码。(不要觉得下面的代码有一堆,具体状态类只要看懂一个就好,因为都是类似的)
具体代码如下:
state.h
#ifndef STATE_H
#define STATE_H
class State
{
public:
State();
State *pNext;
virtual void play()=0;
virtual void stop()=0;
virtual void close()=0;
virtual ~State();
};
#endif // STATE_H
State.cpp里是空的
playstate.h
#ifndef PLAYSTATE_H
#define PLAYSTATE_H
#include "state.h"
#include <QTextEdit>
#include "stopstate.h"
#include "closestate.h"
class PlayState : public State
{
public:
PlayState(QTextEdit *textEdit);
static PlayState *m_instance;
static PlayState *getInstace(QTextEdit *textEdit)
{
if(nullptr == m_instance)
{
m_instance = new PlayState(textEdit);
}
return m_instance;
}
void play();
void stop();
void close();
private:
QTextEdit *m_textEdit;
};
#endif // PLAYSTATE_H
PlayState.cpp
#include "playstate.h"
PlayState *PlayState::m_instance = nullptr;
PlayState::PlayState(QTextEdit *textEdit):m_textEdit(textEdit)
{
}
void PlayState::play()
{
//播放-》播放 do nothing
pNext = this;
}
void PlayState::stop()
{
//播放-》暂停
m_textEdit->setText("Stoped");
pNext = StopState::getInstance(m_textEdit);
}
void PlayState::close()
{
//播放-》关闭
m_textEdit->setText("Closed");
pNext = CloseState::getInstance(m_textEdit);
}
stopstate.cpp
#ifndef STOPSTATE_H
#define STOPSTATE_H
#include "state.h"
#include "playstate.h"
#include "closestate.h"
#include <QTextEdit>
class StopState : public State
{
public:
StopState(QTextEdit *textEdit);
static StopState *m_instance;
static StopState *getInstance(QTextEdit *textEdit)
{
if(nullptr == m_instance)
{
m_instance = new StopState(textEdit);
}
return m_instance;
}
void play();
void stop();
void close();
private:
QTextEdit *m_textEdit;
};
#endif // STOPSTATE_H
StopState.cpp
#include "stopstate.h"
StopState *StopState::m_instance = nullptr;
StopState::StopState(QTextEdit *textEdit):m_textEdit(textEdit)
{
}
void StopState::play()
{
//暂停-》播放
m_textEdit->setText("Playing");
pNext = PlayState::getInstace(m_textEdit);
}
void StopState::stop()
{
//暂停-》暂停 do nothing
pNext = this;
}
void StopState::close()
{
//暂停-》关闭
m_textEdit->setText("Closed");
pNext = CloseState::getInstance(m_textEdit);
}
closestate.h
#ifndef CLOSESTATE_H
#define CLOSESTATE_H
#include "state.h"
#include "stopstate.h"
#include "playstate.h"
#include <QTextEdit>
class CloseState : public State
{
public:
CloseState(QTextEdit *textEdit);
static CloseState *m_instance;
static CloseState *getInstance(QTextEdit *textEdit)
{
if(nullptr == m_instance)
{
m_instance = new CloseState(textEdit);
}
return m_instance;
}
void play();
void stop();
void close();
private:
QTextEdit *m_textEdit;
};
#endif // CLOSESTATE_H
CloseState.cpp
#include "closestate.h"
CloseState *CloseState::m_instance = nullptr;
CloseState::CloseState(QTextEdit *textEdit):m_textEdit(textEdit)
{
}
void CloseState::play()
{
//关闭-》播放
m_textEdit->setText("Playing");
pNext = PlayState::getInstace(m_textEdit);
}
void CloseState::stop()
{
//关闭-》暂停 do nothing
pNext = this;
}
void CloseState::close()
{
//关闭-》关闭 do nothing
pNext = this;
}
statemachine.h
#ifndef STATEMACHINE_H
#define STATEMACHINE_H
#include "state.h"
//此处继承State主要作用是保持接口一致
class StateMachine:public State
{
public:
StateMachine(State *state);
void play();
void stop();
void close();
private:
State *m_curState;
};
#endif // STATEMACHINE_H
StateMachine.cpp
#include "statemachine.h"
StateMachine::StateMachine(State *state):m_curState(state)
{
}
void StateMachine::play()
{
m_curState->play();
m_curState = m_curState->pNext;
}
void StateMachine::stop()
{
m_curState->stop();
m_curState = m_curState->pNext;
}
void StateMachine::close()
{
m_curState->close();
m_curState = m_curState->pNext;
}
创建状态机的代码
void MainWindow::createState2()
{
//初始化状态机,并设置初始状态为播放状态
m_myMachine = new StateMachine(PlayState::getInstace(m_textEdit));
//将按钮点击与状态机转换相连接
//因为将状态转换封装起来了,所以此处不需要再判断当前状态和关心当前状态
connect(m_playBtn,&QPushButton::clicked,this,[=]()
{
m_myMachine->play();
});
connect(m_stopBtn,&QPushButton::clicked,this,[=]()
{
m_myMachine->stop();
});
connect(m_closeBtn,&QPushButton::clicked,this,[=]()
{
m_myMachine->close();
});
}
小结
本来还要了解学习C语言函数指针实现状态机,但是感觉不是特别必要,如果以后真的需要再去了解,网上都有很多例子和教程。
假如写的不对或不合适的地方,请告诉我呀!蟹蟹!抱拳了老铁!