[因为我不懂啊]-什么是状态机编程(设计模式)(2)

先提一下昨天的思路:

  • 电梯包含了四种状态:关门,开门,运动,停止
  • 状态之间会进行转换:关门(默认)->开门->关门->运动->停止->开门->关门

所以,在代码实现时,就出现了一个[状态(枚举)][电梯(类)],其中[电梯]包含了四种状态的处理逻辑,以及提供了一个[去某一层]的方法。状态之间的转换直接写在状态的处理逻辑中(例如开门状态的处理,就是转换到关门状态)。

然后,以此为基础,再进行今天的状态机的学习。


经过一番查阅,我对状态机的理解有一些改变了:
从电梯的例子来看,电梯的运行过程包含了4个状态。如果我在电梯的gotoFloor函数中,使用
if (当前为关门状态) { 关门状态的处理;下一个状态; } else if (当前为开门状态) {开门状态的处理;下一个状态;} else if (当前为运行状态) {运行状态的处理;下一个状态;} ...
这样的方式来进行状态的处理与转换,这也是状态机的一种实现方式。

所以呢?状态机是什么?我的理解就是:状态机就是控制物体状态转换的逻辑,也就是上面这一堆
if () {} else if () {} ...

为什么搜到的多数博客中都把状态机描述得很复杂呢?因为状态机本身就是一套状态转换逻辑,只不过它是为了处理更复杂的物体的状态转换而编写的。

所以状态机的运用,在于怎样合理、高效的实现状态的转换,把原本需要使用大量 if .. else if ..来处理的转换逻辑描述的更清晰。


好了,昨天写的转换看起来还比较清晰(一点也不高端,就是把if...else if...换成了swith...case...),但是如果需要扩展新状态,就要改动原来的swich...case...了。

所以今天换一种实现方式:为每个状态实现一个状态类。


详细设计:

  • 状态类的基类
    * 一个[执行逻辑处理]函数
    * 一个[电梯]对象
    * 一个[设置电梯]函数
  • 关门[状态类]
    * ...
  • 开门[状态类]
    * ...
  • 运动[状态类]
    * ...
  • 停止[状态类]
    * ...
    *电梯[类]
    * 关门状态类[指针]
    * 开门状态类[指针]
    * 运动状态类[指针]
    * 停止状态类[指针]
    * 当前状态类[指针]
    * 设置状态[函数]
    * 按下电梯[函数]

源文件:

//
//  ElevatorTwo.hpp
//  QFLTest
//
//  Created by QuFangliu on 16/9/19.
//
//

#ifndef ElevatorTwo_hpp
#define ElevatorTwo_hpp

#include <stdio.h>

//预先声明一下,在state中需要用到
class ElevatorTwo;

//Elevator has four status : closing, opening, running, stopping
//Four status correspond four classes. All of them inherited from one base state class.
class ElevatorStateBase
{
public:
    virtual ~ElevatorStateBase() {}
    
    //某个状态需要执行的逻辑
    virtual void excute() = 0;
    
    //设置电梯类
    void setElevator(ElevatorTwo *pElevator) { m_pElevator = pElevator; }
protected:
    //保存一个ElevatorTow的指针,为了修改它的状态
    ElevatorTwo *m_pElevator;
};

//四种状态的类
//Closing
class ElevatorStateClosing : public ElevatorStateBase
{
public:
    ElevatorStateClosing();
    virtual ~ElevatorStateClosing();
    
    //执行逻辑
    virtual void excute() override;
};

//Opening
class ElevatorStateOpening : public ElevatorStateBase
{
public:
    ElevatorStateOpening();
    virtual ~ElevatorStateOpening();
    
    //执行逻辑
    virtual void excute() override;
};

//Running
class ElevatorStateRunning : public ElevatorStateBase
{
public:
    ElevatorStateRunning();
    virtual ~ElevatorStateRunning();
    
    //执行逻辑
    virtual void excute() override;
};

//Stopping
class ElevatorStateStopping : public ElevatorStateBase
{
public:
    ElevatorStateStopping();
    virtual ~ElevatorStateStopping();
    
    //执行逻辑
    virtual void excute() override;
};

//电梯类
class ElevatorTwo
{
public:
    ElevatorTwo();
    virtual ~ElevatorTwo();
    
    //对外开放的操作
    void gotoFloor();
    
public:
    //每个状态都有一个class
    ElevatorStateBase *m_pStateClosing = nullptr;
    ElevatorStateBase *m_pStateOpening = nullptr;
    ElevatorStateBase *m_pStateRunning = nullptr;
    ElevatorStateBase *m_pStateStopping = nullptr;

    //设置状态
    void setState(ElevatorStateBase *pState) { m_pState = pState; pState->excute();}
    
private:
    //当前状态
    ElevatorStateBase *m_pState = m_pStateClosing;  //默认关闭状态
};

#endif /* ElevatorTwo_hpp */
//
//  ElevatorTwo.cpp
//  QFLTest
//
//  Created by QuFangliu on 16/9/19.
//
//

#include "ElevatorTwo.hpp"
#include <iostream>

#define QFLLOG(_text_)  do  \
                        {   \
                            std::cout << _text_ << std::endl;   \
                        } while (false) \

//ElevatorStateBase不需要在这里写实现部分了,它是一个抽象类,不能实例化

//Closing
ElevatorStateClosing::ElevatorStateClosing()
{
    m_pElevator = nullptr;
}
ElevatorStateClosing::~ElevatorStateClosing()
{
    
}
void ElevatorStateClosing::excute()
{
    //Closing的逻辑部分
    
    //关门之后,进行一些处理
    QFLLOG("Closing->关上电梯门,等待有人按电梯");
    
    //处理完成,根据条件判断下一状态
    if (rand() % 100 > 50) {
        //假设50%几率,电梯里面没有人(这里的关闭可能是之前一轮运行完后的关闭)
        QFLLOG("Closing->没有要去的楼层,停在这个状态");
    }
    else {
        //50%几率,有人选择了下一个楼层
        QFLLOG("Closing->准备前往下一个目标楼层");
        m_pElevator->setState(m_pElevator->m_pStateRunning);
    }
}

//Opening
ElevatorStateOpening::ElevatorStateOpening()
{
    m_pElevator = nullptr;
}
ElevatorStateOpening::~ElevatorStateOpening()
{
    
}
void ElevatorStateOpening::excute()
{
    //Opening的逻辑部分
    
    //开门之后,进行一些处理
    QFLLOG("Opening->打开电梯门");
    
    //处理完成,下一状态就是关门(转换到下一状态这件事,可以由某些条件触发)
    QFLLOG("Opening->上客、下客完成,准备关门");
    m_pElevator->setState(m_pElevator->m_pStateClosing);
}

//Running
ElevatorStateRunning::ElevatorStateRunning()
{
    m_pElevator = nullptr;
}
ElevatorStateRunning::~ElevatorStateRunning()
{
    
}
void ElevatorStateRunning::excute()
{
    //Running的逻辑部分
    
    //运行到指定的楼层
    QFLLOG("Running->运行前往指定的楼层(加速,匀速,减速)");
    
    //处理完成,下一状态就是保持电梯停止(状态转换事件可以由条件触发)
    QFLLOG("Running->到了指定楼层,准备停止");
    m_pElevator->setState(m_pElevator->m_pStateStopping);
}

//Stopping
ElevatorStateStopping::ElevatorStateStopping()
{
    m_pElevator = nullptr;
}
ElevatorStateStopping::~ElevatorStateStopping()
{
    
}
void ElevatorStateStopping::excute()
{
    //Stopping的逻辑部分
    
    //保持电梯停止
    QFLLOG("Stopping->保持电梯停止(刹车,锁定)");
    
    //处理完成,下一状态就是开门(状态转换可以根据轿箱内是否有人来进行条件触发)
    QFLLOG("Stopping->电梯锁定到楼层,准备开门");
    m_pElevator->setState(m_pElevator->m_pStateOpening);
}

//电梯类
ElevatorTwo::ElevatorTwo()
{
    //初始化自己的几个状态
    m_pStateClosing = new ElevatorStateClosing();
    m_pStateOpening = new ElevatorStateOpening();
    m_pStateRunning = new ElevatorStateRunning();
    m_pStateStopping = new ElevatorStateStopping();
    
    //状态类设置"控制"类
    m_pStateClosing->setElevator(this);
    m_pStateOpening->setElevator(this);
    m_pStateRunning->setElevator(this);
    m_pStateStopping->setElevator(this);
}

ElevatorTwo::~ElevatorTwo()
{
    //注意,要清内存噢
    delete m_pStateClosing;
    delete m_pStateOpening;
    delete m_pStateRunning;
    delete m_pStateStopping;
}

void ElevatorTwo::gotoFloor()
{
    //用户的操作,必定是上或者下,也就是从这个事件开始改变状态
    
    //事件导致的状态就是开门
    this->setState(m_pStateOpening);
}

测试代码:

    //初始化电梯
    auto pElevator = new ElevatorTwo();
    //按电梯咯
    pElevator->gotoFloor();

输出结果(因为用到了随机数,所以输出结果类似,但不唯一):
Opening->打开电梯门
Opening->上客、下客完成,准备关门
Closing->关上电梯门,等待有人按电梯
Closing->准备前往下一个目标楼层
Running->运行前往指定的楼层(加速,匀速,减速)
Running->到了指定楼层,准备停止
Stopping->保持电梯停止(刹车,锁定)
Stopping->电梯锁定到楼层,准备开门
Opening->打开电梯门
Opening->上客、下客完成,准备关门
Closing->关上电梯门,等待有人按电梯
Closing->没有要去的楼层,停在这个状态


示例总结:
1.[电梯]类,其实不需要持有各个状态的指针,当需要转换到某个状态时,生成一个新的状态对象即可。
2.需要扩展状态时,例如[运行]状态的逻辑处理中可能需要新增一个[故障]状态,此时只需要新增一个[故障]状态类,然后在[运行]状态逻辑中,满足故障条件时转换到[故障]状态即可。


总结:
1.状态机,就是一套用于控制物体状态转换的逻辑。实现状态机控制的首要条件就是清楚的划分物体的状态。
2.状态机有各种各样的实现方式,实现时需要考虑到子新增状态。

推荐阅读更多精彩内容