为了进一步学习spring statemachine 本文将继续讲解spring状态机案列CD Player .它类似于现实世界中使用过的CD播放器。CD播放器本身是一个非常简单的实体,它允许用户打开甲板,插入或更换CD,然后按各个按钮调整播放器的功能(出仓和关仓)eject
,(播放)play
, (停止)stop
,(暂停和开始)pause
,(上一曲)back
,和(下一曲)forward
)。
cd player
为了进一步学习spring statemachine 本文将继续讲解spring状态机案列CD Player .它类似于现实世界中使用过的CD播放器。CD播放器本身是一个非常简单的实体,它允许用户打开甲板,插入或更换CD,然后按各个按钮调整播放器的功能(出仓和关仓)eject
,(播放)play
, (停止)stop
,(暂停和开始)pause
,(上一曲)back
,和(下一曲)forward
)。
我们使用过程中很少有人去考虑,怎样去编写与硬件交互以驱动 CD 播放器的代码。是的,我们使用很简单,但是如果我们去看看幕后代码,事情实际上会变得有点复杂。
首先让我们描述下场景:首先我们得有CD播放机和CD库。有了这两样我们就可以播放了,首先弹出舱门eject
然后放入CDcd load
,之后我们可能会直接按play按钮播放(此时应该关仓+播放)或者按关仓后播放,播放之后,播放CD中的当前曲目时间会逐渐+1s,期间我们可以按暂停播放停止播放等行为。
通过简单的描述我们可能已经注意到,如果甲板已经打开并按下播放键,甲板会关闭并开始播放歌曲(如果插入了 CD)。从某种意义上说,当甲板打开时,您首先需要关闭它,然后尝试开始播放(同样,如果确实插入了 CD)。CD 播放器就是如此简单。当然,我们可以用一个简单的类来包装所有这些,这个类有一些bool变量,可能还有一些嵌套的 if-else 子句,这些可以帮助我们完成这项工作,但是如果我们遇到的行为变得更加复杂怎么办?你真的想继续添加更多的标志位和 if-else 子句吗?当然不否则程序维护将更加困难。
流程
状态描述
首先我们描述下cd应该有哪些状态?
1 | 可以分为两大类:工作状态(busy)和空闲状态(idle) |
状态行为图
完整过程
状态机启动,之后我们查找有哪些cd可以用,出仓,放入cd并并关仓,开始播放,然后可以暂停、停止、出仓、换cd等操作。
实现
0、状态和事件定义
1 | // 状态 |
1 | // 事件代表用户可以按下的按钮以及用户是否将光盘加载到播放器中 |
1 | // 扩展状态记录当前播放的哪张cd哪首歌以及歌播放的时间 |
1 | // 上一曲下一曲headers设置TRACKSHIFT |
状态定义
1 |
|
转换定义
1 |
|
在前面的配置中:
EnumStateMachineConfigurerAdapter
配置状态和转换。- 设置
IDLE
的子状态CLOSED
和OPEN
状态,BUSY
的子状态PLAYING
和PAUSED
。 - 对于
CLOSED
状态,我们添加了进入状态的动作closedEntryAction
。 - 状态转换,其中
EJECT
关闭和打开甲板、PLAY
,STOP
和PAUSE
做简单的转换。对于其他转换,我们执行了以下操作:- 对于源状态
PLAYING
,我们添加了一个定时器触发器,自动跟踪播放曲目中的经过时间,并有一个判断会决定何时切换到下一曲目。 - 对于
PLAY
事件,如果源状态是IDLE
,目标状态是BUSY
,我们定义了一个名为 的动作playAction
和一个名为 的守卫playGuard
。 - 对于
LOAD
事件和OPEN
状态,我们使用名为 的动作定义了一个内部转换loadAction
,它跟踪插入带有扩展状态变量的光盘。 - 对于
PLAYING
状态定义了三个内部转换。一个由运行称为 的操作的计时器触发,该操作playingAction
更新扩展状态变量。其他两个转换使用trackAction
不同的事件(分别为BACK
和FORWARD
)来处理用户想要在轨道中后退或前进的情况。
- 对于源状态
1、曲库查询
首先我们得有个查库操作看看有哪些cd唱片。
cd信息
1 | public class Cd { |
曲目信息
1 | public class Track { |
曲库
1 | public class Library { |
加载曲库
1 |
|
2、出仓
3、放入cd
放入cd的时候我们需要记录当前放的是哪一张cd,所以这里有个扩展变量1CD信息
1 | public void load(Cd cd) { |
事件发送之后的处理
1 | .withInternal() |
LoadAction如果事件标头包含有关要加载的光盘的信息,则更新扩展状态变量。以下清单定义了LoadAction:
1 | public static class LoadAction implements Action<States, Events> { |
4、开仓状态下的关仓或播放
这里有两种情况会导致关仓,开仓状态下按下关仓或者play。注意其中play事件触发的关仓,如果播放机里面有cd则进入播放状态
因为关仓后可能会主动进入播放状态,所以我们给关仓close这个状态设置了进入状态的一个动作,主要是判断是不是play事件导致的关仓并且里面有cd唱片
1 | .withStates() |
ClosedEntryAction
是Close状态的入口动作,如果存在光盘,则向状态机CLOSED
发送PLAY
事件。以下定义了ClosedEntryAction
:
1 |
|
5、关仓下的播放
主要判断里面是否有cd,有的话进行播放动作
1 | .withExternal() |
守卫playGuard()
1 |
|
动作playAction()
cd中会有很多歌曲每个歌曲有名称和时长,所以播放我们应该记录播放的是当前cd的哪一首歌以及播放的时长。所以这里又多了两个扩展状态TRACK,ELAPSEDTIME播放那首歌以及时长。
所以扩展变量如下:
1 | public enum Variables { |
进入播放动作后主要初始化一些信息,播放的当前曲目和时间
1 |
|
6、播放中
播放中主要是顺序播放cd中的曲目。这里定时1s触发内部动作
1 | .withInternal() |
playingAction()
1 |
|
7、上一曲目和下一曲目
transational
1 | .withInternal() |
触发事件
1 | public void forward() { |
trackAction()
1 |
|
8、出仓、暂停、停止
发送对应的事件即可
1 | public void stop() { |
1 | public void pause() { |
1 | public void eject() { |
9、查看当前播放的歌曲信息
这个因为内部在不断的定时播放,所以我们应该监听两个转换
1、to busy
说明有cd要播放
1 |
|
2、to playing
说明有歌曲在播放
1 |
|
因为想让这个样本类型安全,所以定义了自己的注释 ( @StatesOnTransition
),它有一个强制性的元注释 ( @OnTransition
)。
1 |
|
测试
1 | sm start |
运行说明:
- 状态机启动,这导致机器被初始化。
- 打印 CD 播放机的
lcd
屏幕状态。 - 打印 CD 库。
- CD 播放机的甲板被打开。
- 将索引为 0 的 CD 装入甲板。
- 播放导致甲板关闭并立即播放,因为插入了光盘。
- 我们打印
lcd
状态并请求下一曲目。 - 我们不玩了。
- 本文作者: 初心
- 本文链接: http://funzzz.fun/2022/03/29/spring-machine-samples-cdplayer/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!