有限状态机问题编程实践(上)

简介: 摘要:一般来说,实体的可能状态是有限的, 在满足一定的条件的情况下触发特定动作会发生实体的状态迁移。对于这类问题,我们一般称为FSM(Finite State Machine), 即有限状态机。本文分享一个有限状态机的java实现,以及使用DSL实现的通用化描述。

在日常开发工作中, 我们在建模时会经常遇到实体在多个状态间进行变迁的问题, 比如:

一个订单的状态可能是 “已下单” , “已支付”, “取消”, “完成” 等,并且在满足一定的条件的情况下触发特定动作会发生实体的状态迁移。一般来说,实体的可能状态是有限的, 对于这类问题,我们一般称为FSM(Finite State Machine), 即有限状态机。举个以前项目的例子:


某种设备通过GPRS连接到控制中心, 并且通过接受控制中心的控制指令来改变自身的运行状态,为了达到这个目的, 设备的底层系统提供了控制设备的API:


image.png




现在要求上层编写设备管理程序, 其状态图如下:


image.png


这是一个典型的状态机实现的问题, 设备拥有多个可能的状态, 并且在特定状态下接受正确的指令后可以迁移到指令的目标状态,直观的看,状态机是一个有向图,状态为端点,操作为边, 一个状态端点在特定操作的驱动下迁移到另一个状态端点。


一份快速的实现

我们用伪代码来描述如下:

define state = shutdown;

do function start();

set state = started;

done;

同时,一个完整的状态变迁图告诉我们如下事实:

  • 一个状态端点可以由哪些状态端点变迁而来
  • 一个状态端点可以变迁到哪些状态端点而去

那么针对这个问题我们可以快速的给出如下的一份实现:

//获取接受到的命令类型

final EventTypeEnum eventType = event.eventType();

//获取接受到的命令内容

final byte[] cmd = event.getInstruction();

switch (eventType) {

//接收到设备启动指令

case BOOT:

switch (currentDeviceStatus) {

//当前状态为关闭状态, 允许启动

case STATUS_POWEROFF:

//获取设备底层API并调用

DeviceRuntime.getDevice().boot(cmd);

currentDeviceStatus =

DeviceStatus.STATUS_BOOTED;

return true;

default:

return false;

}

//接受到设备关闭指令

case POWEROFF:

switch (currentDeviceStatus) {

//当前设备状态为已配置, 允许执行

case STATUS_CONFIURED:

//当前设备状态为已启动, 允许执行

case STATUS_BOOTED:

//当前设备状态为已待机, 允许执行

case STATUS_STANDBY:

//当前设备状态为在网待机,允许执行

case STATUS_LINKED_STANDBY:

DeviceRuntime.getDevice().shutdown(cmd);

currentDeviceStatus =

DeviceStatus.STATUS_POWEROFF;

return true;

default:

return false;

}

//接收到设备激活指令

case ACTIVE:

//...

case CONFIGURE:

//...

case LINK:

//...

case OFFLINE:

//...

case STANDBY:

//...

default:

return false;

我们用2层的switch来约束特定源状态,特定操作,特定目标状态, 如果不考虑状态机的修改,这应该是一个不错的实现,正确,简洁,易读. 但是在应付变化这方面就显得比较无能为力, 原因在于,这个版本的实现缺少结构化的抽象,缺乏职责划分导致无法做到变化的隔离。

考虑一下, 如果我们新增加一个状态, 按照当前的实现, 我们需要做的事情是这样的:

  • 了解新端点是那些边的目标端点
  • 在外层switch中为每条指向新端点的边构造一个switch分支,在内层switch中为每
  • 个源端点构建一个状态变迁实现
  • 了解新增端点是那些原有端点的源
  • 在外层switch中为每条指向原有端点的边构造一个switch分支,在每个内层switch中为新增端点建立一个状态变迁实现


改进后的实现

很明显,正确的完成这件事情并不容易,试想如果一个状态机包含几十个状态,上百个动作,一次要新增或删除数个节点,改动和测试的复杂度将非常高。

下面我们来改进上面的实现, 前面讲过, 一个状态端点在特定操作的驱动下迁移到另一个状态端点, 这是状态机的唯一行为模式,是稳定的, 不稳定的是新增状态时的前置约束,指令的新增以及状态的变化, 所以从大体上我们需要把稳定的行为和不稳定的行为进行隔离和抽象。

首先, 我们再来对一次状态变迁进行分析:



image.png


一次状态迁移必然包含如下要素:

  • 迁移的事件类型是什么? 比如启动, 激活, 关闭等
  • 迁移的原状态时什么? 比如 启动类型的迁移, 其原状态必须是已关闭
  • 迁移结束后的状态是什么? 比如 启动类型的迁移, 其目标状态时已启动
  • 迁移指令如何执行

如果我们从这个角度来进行抽象, 我们需要一个操作模型来封闭起始状态和指令的执行,那么我们首先来定义一个设备的操作:

public interface DeviceOperation {

/**

* 返回此操作代表的类型

* @return 操作代表的类型

*/

public EventTypeEnum operationType();

/**

* 声明设备在什么状态下允许执行此操作

* @return 操作可执行的设备状态

*/

public Set<DeviceStatus> avaliableStatus();

/**

* 声明如果此指令操作成功, 设备应该处于的下一个状态

* @return 设备的新状态

*/

public DeviceStatus nextStatus();

/**

* 执行指令操作

* @param cmd 指令数据

*/

public void doOperation(final byte[] cmd);

}

一个操作(边)有它自己的类型, 有执行指令的实现,有目标端点和源端点。所有的要素都有了, 我们可以这样描述一个具体实现

操作X -> "操作X的类型为 operationType() , 在当前状态为 avaliableStatus()之一时允许执行指令操作doOperation(#cmd#), 成功执行后设备状态更改为nextStatus()"

由于指令模型再执行指令时向上表现为一致的行为方式, 而底层设备为不同指令提供了不同的API, 因此在真正执行指令操作的地方, 我们也需要进行抽象和适配(下图仅列出部分操作):


image.png



相关文章
|
编译器
GEE脚本——GEE中如何查询历史脚本和防丢失记录
GEE脚本——GEE中如何查询历史脚本和防丢失记录
788 4
|
SQL 算法 关系型数据库
MySQL8.0大表秒加字段,是真的吗?
很早就听说 MySQL8.0 支持快速加列,可以实现大表秒级加字段。笔者自己本地也有8.0环境,但一直未进行测试。本篇文章我们就一起来看下 MySQL8.0 快速加列到底要如何操作。
1475 0
|
机器学习/深度学习 人工智能 自然语言处理
C++构建 GAN 模型:生成器与判别器平衡训练的关键秘籍
生成对抗网络(GAN)是AI领域的明星,尤其在C++中构建时,平衡生成器与判别器的训练尤为关键。本文探讨了GAN的基本架构、训练原理及平衡训练的重要性,提出了包括合理初始化、精心设计损失函数、动态调整学习率、引入正则化技术和监测训练过程在内的五大策略,旨在确保GAN模型在C++环境下的高效、稳定训练,以生成高质量的结果,推动AI技术的发展。
406 10
|
消息中间件 Kubernetes 调度
k8s教程(pod篇)-批处理调度
k8s教程(pod篇)-批处理调度
420 0
|
小程序 安全 数据安全/隐私保护
理发店预约小程序开发:随时随地,省时省力
理发店预约小程序开发要点:集成预约系统,用户填写信息并自动匹配时间及理发师;包含充值功能,支持安全支付及多种折扣;用户评价系统确保服务质量透明;发型展示帮助用户选择,支持模拟试戴;重视用户体验,界面友好,加载速度快;确保数据安全,兼容多平台,定期更新以优化性能和响应用户需求。寻求开发合作可联系相关人员。
|
人工智能 API 开发者
免费使用Kimi的API接口,kimi-free-api真香
今年AI应用兴起,各类智能体涌现,但API免费额度有限。为解决这一问题,GitHub上的[kimi-free-api](https://github.com/LLM-Red-Team/kimi-free-api)项目提供了方便,支持高速流式输出、多轮对话等,与ChatGPT接口兼容。此外,还有其他大模型的免费API转换项目,如跃问StepChat、阿里通义Qwen等。该项目可帮助用户免费体验,通过Docker-compose轻松部署。只需获取refresh_token,即可开始使用。这个开源项目促进了AI学习和开发,为探索AI潜力提供了新途径。
3895 3
|
设计模式 架构师 安全
如何提高自己的架构设计能力?
提升架构设计能力涉及深入学习基础知识、业务理解、技术广度与深度、实践经验等多方面。要关注代码的清晰结构、抽象能力、系统性能和可扩展性。学习编程语言、设计模式、系统设计原则和分布式系统是关键。通过实际项目和不断学习反思,可以增强架构设计技能。例如,上述代码展示了清晰的结构和设计原则应用。
1371 0
|
存储 SQL 关系型数据库
认真学习MySQL中的那些日志文件-通用查询日志&错误日志
认真学习MySQL中的那些日志文件-通用查询日志&错误日志
1178 1
|
存储 网络协议 分布式数据库
网络名词术语解析 | 路由、交换机、集线器、半/全双工、DNS、LAN、WAN、端口、MTU
网络名词术语解析 | 路由、交换机、集线器、半/全双工、DNS、LAN、WAN、端口、MTU
780 0
|
芯片
PCIe 均衡技术介绍(电气物理篇)
PCIe 均衡技术介绍(电气物理篇)
10396 0
PCIe 均衡技术介绍(电气物理篇)