State Machine Compiler 用法:使用 SMC 工具自动生成状态机代码
State Machine Compiler 用法:使用 SMC 工具自动生成状态机代码
Qidi 2020.12.18 (MarkDown & EnterpriseArchitect & Haroopad)
0. SMC 简介
SMC (State Machine Compiler) 目前还没有正式中文名,这里把它意译成 状态机生成器,其主要作者是 Charles W. Rapp。状态机生成器实际上早在 2000 年左右就由 Robert C. Martin (他还有个网名叫 Uncle Bob) 创造出来了,现在一般把他发明的状态机生成器称作 OSMC (Original State Machine Compiler)。Charles 曾经是 Robert 的同事,在后者离职后,前者接手了 OSMC 的维护工作[1]。
本文介绍的 SMC 使用 Java 写成,在 OSMC 的基础上进行了扩展和优化,可用于生成基于 状态模式(State Pattern) 实现的状态机代码,并且支持多达 14 种编程语言[2]。相关源码可以在 SourceForge.net 上查看。如果对 SMC 的代码不感兴趣,也可以直接下载最新版本的 jar 包使用(点我下载)。
1. 为 SMC 配置运行环境
下载并解压后,得到可执行文件 Smc.jar,但需要先安装 Java 运行环境才能运行它。
安装 JRE 的教程网上很多,读者可以自行搜索,或者参考《JRE的安装及环境变量配置》这篇文章进行操作。
2. 选定目标场景,确定状态迁移关系
比如我们对 干饭人 混吃等死的美好生活很感兴趣,想实现一个表现 干饭人的一天 的状态机。经过一段时间遐想后,可以得出和干饭人直接相关的状态有:
- 饿了
- 渴了
- 吃饭
- 喝水
- 闲逛
- 睡觉
- 满足
其中 满足 可以作为干饭人的起始状态,视天气而定,转换到 闲逛 或者 睡觉 状态,在这两种状态下,随时间流逝又可以转换为 饿了 或者 渴了 状态,继而转换到 吃饭 或者 喝水 状态。吃掉一定量的食物后(喝水同理),干饭人有可能还没吃饱,这时就会回到 饿了 的状态;或者有可能已经吃饱但又口渴了,这时就会转换到 渴了 的状态;如果此时既不饿也不渴,就会转回到 满足 的状态,实现一次循环。
如此,我们就可以手绘出一幅状态迁移图,如下所示:

3. 编写 .sm 文件
理清状态迁移关系之后,为了下一步使用 SMC 工具自动生成代码,我们就可以开始编写 .sm 文件了。
3.1 .sm 文件的格式和语法
关于 .sm 文件的格式和语法,详细说明可以参考 官方文档(点我阅读)。本文只对基础用法进行说明,搭配 3.2节 中的 示例文件 进行阅读,效果更佳。
-
格式
- 添加源文件版权声明/免责声明
使用
%{...}%将声明内容括起来进行标识,比如:%{ // put disclaimer or copy right here %}- 添加注释
使用
//进行标识,比如:Thirsty {// to drinkprocessStateDrinking{} }- 添加具体实现类的类名
用于实现状态机中的具体行为的类的名字。用
%class关键字来标识,比如:%class EatMealMan- 添加声明具体实现类的头文件名
包含具体实现类的类声明的头文件的名字。用
%header关键字来标识,比如:%header EatMealMan.h- 指定要生成的状态机类的类名
要生成的状态机类的名字。用
%fsmclass关键字来标识,比如:%fsmclass EatMealManFSM- 指定要生成的状态机类所在的源文件名
要生成的包含状态机类实现的源文件名(不含扩展文件名)。用
%fsmfile关键字来标识,比如:%fsmfile EatMealManFSM- 指定状态机起始状态
使用
%start关键字来标识,比如:%start EatMealManMap::Satisfied- 指定状态迁移图名称
使用
%map关键字来标识,比如:%map EatMealManMap- 添加各状态定义及迁移关系
使用
%%...%%将各状态及其迁移关系扩起来进行标识,比如:%% Satisfied {processState [!context.getOwner().isGoodWeather()]Sleeping{}processStateWandering{} }Wandering {processStateSatisfied{hangOutAWhile(ctxt.getStateDuration());} }Sleeping {processStateSatisfied{sleepAWhile(ctxt.getStateDuration());} } %% -
语法
- 语法总览
StateNameEntry{// 在这里添加进入 StateName 状态时的行为}Exit{// 在这里添加退出 StateName 状态时的行为} {TriggerMethod [triggerConditions]NewStateName{// 在这里添加转换到 NewStateName 状态前的行为} }其中
StateName是当前的状态名,NewStateName是要切换到的状态名;
Entry和Exit及它们的行为定义都是可选的,如果没有需要可以不写;
TriggerMethod是触发状态转换的方法名,triggerCondition是转换到NewStateName状态需要满足的条件。triggerCondition也是可选的。- 简单状态迁移
只需定义当前状态、目标状态、触发方法。如下:
Thirsty {// to drinkprocessStateDrinking{} }- 条件性状态迁移
在简单状态迁移写法的基础上,在
TriggerMethod后,使用 方括号[]添加状态转换的保护条件(Guard Condition)。只有当条件满足时,才会进行状态切换。如下:Sleeping {processState [context.getOwner().isThirsty()]Thirsty{}processState [context.getOwner().isHungry()]Starving{}processStateSatisfied{} }在多个相同的
TriggerName后添加不同的保护条件和目标状态,其含义为:在该函数中,满足不同条件时分别切换到不同的状态。- 指定转换到新状态前的行为
在目标状态的花括号中添加的行为,会在转换到目标状态前被执行。如下:
Thirsty {// to drinkprocessStateDrinking{setThirstyFlag(true);} }- 带参数的状态迁移
在
TriggerMethod后,使用 圆括号()添加触发函数的参数。如下:Thirsty {maintenanceTime(isBlocked: bool)nil{startCLI(isBlocked);} }其中
maintenanceTime是触发函数的名字,isBlocked是函数参数,bool是参数类型。这个函数的作用是使干饭人进入到维护模式,以便开发人员实时检查运行状态。nil表示这个函数不会触发状态切换,状态机会将当前状态设置为最终状态。- 指定进入状态时的行为
使用
Entry关键字来指定进入状态时的行为。如下:EatingEntry{eatBread(ctxt.getBreadSupply());} {// still hungry after eatingprocessState [context.getOwner().isHungry()]Starving{}// feel thirsty after eatingprocessState [context.getOwner().isThirsty()]Thirsty{setThirstyFlag(true);}// well fedprocessStateSatisfied{setStarvingFlag(false);} }这个示例表示:进入
Eating状态时,就执行eatBread()函数。- 指定退出状态时的行为
类似的,使用
Exit关键字来指定退出状态时的行为。如下:SatisfiedExit{saySomething();} {// raining outsideprocessState [!context.getOwner().isGoodWeat
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
