通过Qt状态机框架、设计模式(状态模式)实现状态转换

起因

 那天下班吃完饭回宿舍,发现项目组长(住我隔壁屋)在打电话。我一开始没有在意,进屋收拾东西,谁知道面试完了之后组长喊了我一声,说刚电话面试完一个求职者,巴啦啦说了一堆,意思就是想考考我。
 题目就是关于录音机各个按键切换,关于这个情景有什么想法。
 说实话,我并没有一开始想到状态机,最后组长提醒我这算是很经典的状态机应用场景。然后我就开始思考,然后学习了一番。

示例说明

 试下类似如下图效果:在这里插入图片描述
 简单实现了播放、暂停和关闭三种状态之间的转换。具体转换过程:播放->关闭,关闭->播放,播放->暂停,暂停->播放,关闭->播放。

 如果觉得代码有点多,可以下载下来看,看文章里的也一样。只是现在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语言函数指针实现状态机,但是感觉不是特别必要,如果以后真的需要再去了解,网上都有很多例子和教程。
 假如写的不对或不合适的地方,请告诉我呀!蟹蟹!抱拳了老铁!

  • 7
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值