[因为我不懂啊]-什么是状态机编程(设计模式)(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.状态机有各种各样的实现方式,实现时需要考虑到子新增状态。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,710评论 4 376
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,839评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,295评论 0 255
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,776评论 0 223
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,198评论 3 297
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,074评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,200评论 2 322
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,986评论 0 214
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,733评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,877评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,348评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,675评论 3 265
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,393评论 3 246
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,209评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,996评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,212评论 2 287
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 36,003评论 2 280

推荐阅读更多精彩内容