一.引言言

有限状态机是一种用来进行对象行为建模的工具,其作用主要是描述对象在它的生命周期内所经历的状态序列,以及如何响应来自外界的各种事件。在面向对象的软件系统中,一个对象无论多么简单或者多么复杂,都必然会经历一个从开始创建到最终消亡的完整过程,这通常被称为对象的生命周期。一般说来,对象在其生命期内是不可能完全孤立的,它必须通过发送消息来影响其它对象,或者通过接受消息来改变自身。在大多数情况下,这些消息都只不过是些简单的、同步的方法调用而已。例如,在银行客户管理系统中,客户类(Customer)的实例在需要的时候,可能会调用帐户(Account)类中定义的getBalance()方法。在这种简单的情况下,类Customer并不需要一个有限状态机来描述自己的行为,主要原因在于它当前的行为并不依赖于过去的某个状态。[1]

遗憾的是并不是所有情况都会如此简单,事实上许多实用的软件系统都必须维护一两个非常关键的对象,它们通常具有非常复杂的状态转换关系,而且需要对来自外部的各种异步事件进行响应。例如,在VoIP电话系统中,电话类(Telephone)的实例必须能够响应来自对方的随机呼叫,来自用户的按键事件,以及来自网络的信令等。在处理这些消息时,类Telephone所要采取的行为完全依赖于它当前所处的状态,因而此时使用状态机就将是一个不错的选择。[1]

游戏引擎是有限状态机最为成功的应用领域之一,由于设计良好的状态机能够被用来取代部分的人工智能算法,因此游戏中的每个角色或者器件都有可能内嵌一个状态机。考虑RPG游戏中城门这样一个简单的对象,它具有打开(Opened)、关闭(Closed)、上锁(Locked)、解锁(Unlocked)四种状态,如图1所示。当玩家到达一个处于状态Locked的门时,如果此时他已经找到了用来开门的钥匙,那么他就可以利用它将门的当前状态转变为Unlocked,进一步还可以通过旋转门上的把手将其状态转变为Opened,从而成功地进入城内。[1]

图1控制城门的状态机

在描述有限状态机时,状态、事件、转换和动作是经常会碰到的几个基本概念。

状态(State)指的是对象在其生命周期中的一种状况,处于某个特定状态中的对象必然会满足某些条件、执行某些动作或者是等待某些事件。

事件(Event)指的是在时间和空间上占有一定位置,并且对状态机来讲是有意义的那些事情。事件通常会引起状态的变迁,促使状态机从一种状态切换到另一种状态。

转换(Transition)指的是两个状态之间的一种关系,表明对象将在第一个状态中执行一定的动作,并将在某个事件发生同时某个特定条件满足时进入第二个状态。

   动作(Action)指的是状态机中可以执行的那些原子操作,所谓原子操作指的是它们在运行的过程中不能被其他消息所中断,必须一直执行下去。

二、基于传统C语言的FSM实现技术

2.1、基于switch(状态)的实现

在实现有限状态机时,使用switch语句是最简单也是最直接的一种方式,其基本思路是为状态机中的每一种状态都设置一个case分支,专门用于对该状态进行控制。下面的代码示范了如何运用switch语句,来实现图1中所示的状态机:

 
switch (state)  {
  // 处理状态Opened的分支
  case (Opened): {
    // 执行动作Open
    open();
    // 检查是否有CloseDoor事件
    if (closeDoor()) { 
      // 当前状态转换为Closed
      changeState(Closed)
    }
    break;
  } 
  // 处理状态Closed的分支
  case (Closed): {
    // 执行动作Close
    close();
    // 检查是否有OpenDoor事件
    if (openDoor()) {
      // 当前状态转换为Opened
      changeState(Opened);
    }
    // 检查是否有LockDoor事件
    if (lockDoor()) {
      // 当前状态转换为Locked
      changeState(Locked);
    }
    break;
  }
 
  // 处理状态Locked的分支
  case (Locked): {
    // 执行动作Lock
    lock();
    // 检查是否有UnlockDoor事件
    if (unlockDoor()) {
      // 当前状态转换为Unlocked
      changeState(Unlocked);
    }
    break;
  }
 
  // 处理状态Unlocked的分支
  case (Unlocked): {
    // 执行动作Unlock
    unlock();
    // 检查是否有LockDoor事件
    if (lockDoor()) {
      // 当前状态转换为Locked    
      changeState(Locked)
    }
    // 检查是否有OpenDoor事件    
    if (openDoor()) {
      // 当前状态转换为Opened
      changeSate(Opened);
    }
    break;
  } 
}

使用switch语句实现的有限状态机的确能够很好地工作,但代码的可读性并不十分理想,主要原因是在实现状态之间的转换时,检查转换条件和进行状态转换都是混杂在当前状态中来完成的。例如,当城门处于Opened状态时,需要在相应的case中调用closeDoor()函数来检查是否有必要进行状态转换,如果是的话则还需要调用changeState()函数将当前状态切换到Closed。显然,如果在每种状态下都需要分别检查多个不同的转换条件,并且需要根据检查结果让状态机切换到不同的状态,那么这样的代码将是枯燥而难懂的。从代码重构的角度来讲,此时更好的做法是引入checkStateChange()和performStateChange()两个函数,专门用来对转换条件进行检查,以及激活转换时所需要执行的各种动作。这样一来,程序结构将变得更加清晰:

 
switch (state)  {
 
  // 处理状态Opened的分支
  case (Opened): {
    // 执行动作Open
    open();
    // 检查是否有激发状态转换的事件产生
    if (checkStateChange()) {
      // 对状态机的状态进行转换
      performStateChange();
    }
    break;
  } 
  // 处理状态Closed的分支
  case (Closed): {
    // 执行动作Close
    close();
    // 检查是否有激发状态转换的事件产生
    if (checkStateChange()) {
      // 对状态机的状态进行转换
      performStateChange();
    }
    break;
  }
 
  // 处理状态Locked的分支
  case (Locked): {
    // 执行动作Lock
    lock();
    // 检查是否有激发状态转换的事件产生
    if (checkStateChange()) {
      // 对状态机的状态进行转换
      performStateChange();
    }
    break;
  }
 
  // 处理状态Unlocked的分支
  case (Unlocked): {
    // 执行动作Lock
    unlock();
    // 检查是否有激发状态转换的事件产生
    if (checkStateChange()) {
      // 对状态机的状态进行转换
      performStateChange();
    }
    break;
  } 
}

但checkStateChange()和performStateChange()这两个函数本身依然会在面对很复杂的状态机时,内部逻辑变得异常臃肿,甚至可能是难以实现。

在很长一段时期内,使用switch语句一直是实现有限状态机的唯一方法,甚至像编译器这样复杂的软件系统,大部分也都直接采用这种实现方式。但之后随着状态机应用的逐渐深入,构造出来的状态机越来越复杂,这种方法也开始面临各种严峻的考验,其中最令人头痛的是如果状态机中的状态非常多,或者状态之间的转换关系异常复杂,那么简单地使用switch语句构造出来的状态机将是不可维护的。

三、基于面向对象的FSM实现技术

3.1、用一个类实现FSM

class DoorFSM {

private:

States __Y;

std::queue<Event> __events;

void __processEvent( Event e );

protected:

virtual void enterOpened() = 0;

virtual void enterLocked() = 0;

virtual void enterUnlocked() = 0;

virtual void enterClosed() = 0;

public:

/* States */

enum States { Closed, Unlocked, Locked, Opened   }; // states

/*Events*/

enum Event { Lock, Unlock, Open, Close   };

/// Constructor

DoorFSM() { __Y = Opened; }

/// Destructor

virtual ~DoorFSM() {}

/** Get current FSM state

@returns current FSM state

*/

States currentState() { return __Y; }

/** Send event to FSM

Use this function to send event to DoorFSM After you call it given event will be handled, and, if some of transition conditions match, appropriate transition will be triggered, and currentState() will be changed. If this function is called during existing event handling process, given event will be added to pending event queue, and will be handled after current transition. See examples for details.

*/

void A( Event e );

};

void DoorFSM::__processEvent( Event e )

{

States yOld = __Y;

bool pass = false;

switch( __Y ) { //transitions

case Closed:

if( e == Open ) {

//outcome actions

__Y = Opened;

pass = true;

}

else if( e == Lock ) {

//outcome actions

__Y = Locked;

pass = true;

}

break;

case Unlocked:

if( e == Lock ) {

//outcome actions

__Y = Locked;

pass = true;

}

else if( e == Open ) {

//outcome actions

__Y = Opened;

pass = true;

}

break;

case Locked:

if( e == Unlock ) {

//outcome actions

__Y = Unlocked;

pass = true;

}

break;

case Opened:

if( e == Close ) {

//outcome actions

__Y = Closed;

pass = true;

}

break;

}

if( yOld == __Y && !pass ) { return; }

switch( __Y ) { // income actions

case Closed:

enterClosed();

break;

case Unlocked:

enterUnlocked();

break;

case Locked:

enterLocked();

break;

case Opened:

enterOpened();

break;

}

}

void DoorFSM::A( Event e )

{

bool __empty = __events.empty();

__events.push( e );

if( __empty ) {

while( !__events.empty() ) {

__processEvent( __events.front() );

__events.pop();

}

}

}

class DoorFSMLogic : public DoorFSM

{

protected:

virtual void enterOpened(){std::cout << "Enter Opened state." << std::endl;}

virtual void enterLocked() {std::cout << "Enter Closed state." << std::endl;}

virtual void enterUnlocked() {std::cout << "Enter Locked state." << std::endl;}

virtual void enterClosed() {std::cout << "Enter Unlocked state." << std::endl;}

};

测试程序

int main()

{

DoorFSMLogic door;

door.A(DoorFSM::Close);

door.A(DoorFSM::Lock);

door.A(DoorFSM::Unlock);

door.A(DoorFSM::Open);

}

基于C++有限状态机的实现技术相关推荐

  1. 基于Python的验证码识别技术

    基于Python的验证码识别技术 作者:强哥 概述 前言 准备工作 识别原理 图像处理 切割图像 人工标注 训练数据 检测结果 搞笑一刻 福利一刻 推荐阅读 前言 很多网站登录都需要输入验证码,如果要 ...

  2. 视频来了 | 基于结构光投影三维重建技术系列课程

    写在前面: 话不多说,这里给大家推荐下我们的知识星球「3D视觉从入门到精通」. 学习3D视觉核心技术,扫描查看介绍,3天内无条件退款 圈里有高质量教程资料.可答疑解惑.助你高效解决问题 感谢大家对工坊 ...

  3. 直播回顾|基于格雷码结合相移技术的高鲁棒性高效率动态三维面形测量

    点击上方"3D视觉工坊",选择"星标" 干货第一时间送达 大家好,本公众号现已开启线上视频公开课,主讲人通过B站直播间(bilibili号:3D视觉工坊:htt ...

  4. 重磅直播|基于格雷码结合相移技术的高鲁棒性高效率动态三维面形测量

    点击上方"3D视觉工坊",选择"星标" 干货第一时间送达 大家好,本公众号现已开启线上视频公开课,主讲人通过B站直播间,对3D视觉领域相关知识点进行讲解,并在微 ...

  5. 读“基于深度学习的图像识别技术研究综述”有感

    "基于深度学习的图像识别技术研究综述"总结 现在流行的图像识别技术都是基于深度学习的算法,经过前辈们的探索改进,图像识别技术经历很多阶段,现如今图像识别技术已经广泛的应用于生活的方 ...

  6. 基于VMM的Rootkit检测技术及模型分析

    Linux通过其特有的虚拟文件系统(Virtual Filesystem)实现对多种文件系统的兼容.虚拟文件系统又称虚拟文件系统转换(Virtual Filesystem Switch vFs),是一 ...

  7. AI:2020年6月22日北京智源大会演讲分享之机器感知专题论坛—13:30-14:10山世光教授《从看脸到读心:基于视觉的情感感知技术》

    AI:2020年6月22日北京智源大会演讲分享之机器感知专题论坛-13:30-14:10山世光教授<从看脸到读心:基于视觉的情感感知技术> 导读:首先感谢北京智源大会进行主题演讲的各领域顶 ...

  8. Dataset之MNIST:MNIST(手写数字图片识别+ubyte.gz文件)数据集的下载(基于python语言根据爬虫技术自动下载MNIST数据集)

    Dataset之MNIST:MNIST(手写数字图片识别+ubyte.gz文件)数据集的下载(基于python语言根据爬虫技术自动下载MNIST数据集) 目录 数据集下载的所有代码 1.主文件 mni ...

  9. 基于php和服务器推技术的web即时聊天系统,基于php和服务器推技术的Web即时聊天系统...

    基于php和服务器推技术的Web即时聊天系统① 王振兴, 黄静 [摘要]摘要: 基于http协议应用于Web端, 实现一个浏览器无关的.便于移植的.高性能的Web即时聊天系统. 系统使用服务器推技术中 ...

最新文章

  1. 徐匡迪、潘云鹤等纷纷撰文,关于人工智能的最新判断都在这里了
  2. Flask之扩展flask-migrate
  3. 登录mysql出现/var/lib/mysql/mysql.sock不存在
  4. python paramiko模块
  5. ElementUI中的el-form怎样格式化显示1和0为是和否
  6. java 有哪些反射机制_Java 的反射机制你了解多少?
  7. 什么时候用不到索引?
  8. 分布式数据库TiDB是如何结合OLTP和OLAP的?
  9. P4245 【模板】任意模数多项式乘法
  10. 使用git将本地文件上传到远程仓库
  11. HTML5中Nav元素作用及应用场景知识点
  12. C++提高部分_C++普通函数与函数模板调用规则---C++语言工作笔记085
  13. 速成pytorch学习——9天构建模型的3种方法
  14. error: failed to launch '/private/var/mobile/Applications/** busy解决办法
  15. bc547可以用8050代换吗_怀孕可以用婴儿护肤品吗?
  16. Java基础知识面试题
  17. 三星c7000 android8,三星C7000官方固件rom刷机包-C7000ZCU3CRI1 安卓8.0
  18. 【毫米波雷达】LFMCW测距/测速原理
  19. JAVA合并两个PDF文件
  20. 2019厦门科技中学计算机特长,注意看!2019厦门市属学校特长生录取入围名单公布!...

热门文章

  1. 将下图的nfa确定化为dfa_作业8 非确定的自动机NFA确定化为DFA
  2. 今天开始学模式识别与机器学习Pattern Recognition and Machine Learning (PRML),章节1.2,Probability Theory (下)
  3. 2021MIT博士pluskid年终总结
  4. 图解GitHub和SourceTree入门教程
  5. Jmeter教程 简单的压力测试
  6. 你拍的最让你「惊喜」的照片是哪张?
  7. 那些计算机界的伟大女性
  8. Ubuntu启用休眠hibernate
  9. Java序列化,看这篇就够了!
  10. 用C语言扩展Python的功能