[翻译]AKKA笔记 - 有限状态机 -1
原文地址:http://rerun.me/2016/05/21/akka-notes-finite-state-machines-1/
我最近有个机会在工作上使用了Akka FSM,是个非常有趣的例子。API(实际上就是DSL),使用体验很棒。这里是我尝试用Akka FSM的有限状态机来写日志。作为例子,我们会以构建一个咖啡机的步骤作为例子。
为什么不用BECOME和UNBECOME
我们知道plain vanilla Akka Actor可以用become/unbecome切换行为。那么,为什么我们需要Akka FSM?不能简单点用Actor在状态间切换? 当然可以。但是当Akka的become and unbecome被一堆状态搅在一起并不停地切换状态的时候,建一个有许多状态的状态机能让代码迅速变的异常难懂(并且难调试)。
没啥奇怪的,常见的建议就是当你在Actor时使用超过2种状态就切换到Akka FSM。
AKKA FSM是啥
Akka FSM是Akka用来简化管理Actor中不同状态和切换状态而构建有限状态机的方法。
在底层,Akka FSM就是一个继承了Actor的trait。
trait FSM[S, D] extends Actor with Listeners with ActorLogging
FSM trait提供的是纯魔法 - 他提供了一个包装了常规Actor的DSL,让我们能集中注意力在更快的构建手头的状态机上。
换句话说,我们的常规Actor只有一个receive方法,FSM trait包装了receive方法的实现并将调用指向到一个特定状态机的处理代码块。
在我写完代码后注意的另一个事,就是完整的FSM Actor仍然很干净并易懂。
现在让我们开始看代码。之前说过,我们要用Akka FSM建一个咖啡机。状态机是这样的:
状态和数据
在FSM中,有两个东西是一直存在的 - 任何时间点都有状态 ,和在状态中进行共享的数据。 在Akka FSM,想要校验哪个是自己的数据,哪个是状态机的数据,我们只要检查这个声明。
class CoffeeMachine extends FSM[MachineState, MachineData]
这代表所有的fsm的状态继承自MachineState,而所有在状态间共享的数据就是MachineData。
作为一种风格,跟普通Actor一样我们在companion对象中声明所有的消息,所以我们在companion对象中声明了状态和数据:
object CoffeeMachine {
sealed trait MachineState
case object Open extends MachineState
case object ReadyToBuy extends MachineState
case object PoweredOff extends MachineState
case class MachineData(currentTxTotal: Int, costOfCoffee: Int, coffeesLeft: Int)
}
在状态机的图中,我们有三个状态 - 打开,可买和关闭。 我们的数据,MachineData保留了开飞机关闭前机器中咖啡的数量(coffeesLeft),每杯咖啡的价格(costOfCoffee),咖啡机存放的零钱(currentTxTotal) - 如果零钱比咖啡价格低,机器就不卖咖啡,如果多,那么我们能找回零钱。
关于状态和数据就这么多了。
在我们看每个状态机的实现和用户可用状态机做的交互前, 我们先在5万英尺看下FSM Actor。
FSM ACTOR的结构
FSM Actor的结构看起来跟我们的状态机图的差不多:
class CoffeeMachine extends FSM[MachineState, MachineData] {
//What State and Data must this FSM start with (duh!)
startWith(Open, MachineData(..))
//Handlers of State
when(Open) {
...
...
when(ReadyToBuy) {
...
...
when(PoweredOff) {
...
...
//fallback handler when an Event is unhandled by none of the States.
whenUnhandled {
...
...
//Do we need to do something when there is a State change?
onTransition {
case Open -> ReadyToBuy => ...
...
...
}
我们能从结构中看出什么:
1)我们有一个初始状态(Open),when(open)代码块处理Open状态的
收到的消息,ReadyToBuy状态由when(ReadyToBuy)代码块来处理。我提到的消息与常规我们发给Actor的消息时一样的,消息与数据一起包装过。包装后的叫做Event(akka.actor.FSM.Event),看起来的样例是这样Event(deposit: Deposit, MachineData(currentTxTotal, costOfCoffee, coffeesLeft))
Akka的文档介绍:
/**
* All messages sent to the [[akka.actor.FSM]] will be wrapped inside an
* `Event`, which allows pattern matching to extract both state and data.
*/
case class Event[D](event: Any, stateData: D) extends NoSerializationVerificationNeeded
2)我们还能看到when方法接受两个参数 - 第一个是状态的名字,如Open,ReadyToBuy,另一个参数是PartialFunction, 与Actor的receive方法一样做模式匹配。最重要的事是每一个模式匹配的case块必须返回一个状态(下次会讲)。所以,代码块会是这样的
when(Open) {
case Event(deposit: Deposit, MachineData(currentTxTotal, costOfCoffee, coffeesLeft)) => {
...
...
3)基本上, 消息中匹配到了when中第二个参数的模式会被一个特定状态来处理。如果没有匹配到,FSM Actor会尝试将我们的消息与whenUnhandled块中的模式进行匹配。理论上,所有在模式中没有匹配到的消息都会被whenUnhandled处理。(我倒不太想建议编码风格不过你可以声明小点的PartialFunction并用andThen组合使用它,这样你就能在选好的状态中重用模式匹配。)
4)最后,还有个onTransition方法能让你在状态变化时做出反应或得到通知。
交互/消息
会有两类人与咖啡机交互,喝咖啡的人,需要咖啡和咖啡机,和维护咖啡机做管理工作的人。
为了便于管理,所有与机器的交互里我用了两个trait。(再提一下,一个交互/消息是与MachineData一起并被包在Event中的第一个元素。在原来的老Actor协议中,这个与发消息给Actor是一样的。
object CoffeeProtocol {
trait UserInteraction
trait VendorInteraction
...
...
供应商交互
让我们也声明一下供应商可以与机器做的交互。
case object ShutDownMachine extends VendorInteraction
case object StartUpMachine extends VendorInteraction
case class SetCostOfCoffee(price: Int) extends VendorInteraction
//Sets Maximum number of coffees that the vending machine could dispense
case class SetNumberOfCoffee(quantity: Int) extends VendorInteraction
case object GetNumberOfCoffee extends VendorInteraction
所以,供应商可以
- 打开或关闭机器
- 设置咖啡的价格
- 设置和拿到机器中已有咖啡的数量。
用户交互
case class Deposit(value: Int) extends UserInteraction
case class Balance(value: Int) extends UserInteraction
case object Cancel extends UserInteraction
case object BrewCoffee extends UserInteraction
case object GetCostOfCoffee extends UserInteraction
那么,对于用户交互, 用户可以
- 存钱买一杯咖啡
- 如果钱比咖啡的价格高那么可以得到找零
- 如果存的钱正好或高于咖啡价格机器就可以让咖啡机做咖啡
- 在煮咖啡前取消交易过程并拿到所有的退款
- 问机器查询咖啡的价格
下一篇,我们会看下所有的状态并研究下他们的交互。
代码
代码在github.
文章来自微信平台「麦芽面包」
微信公众号「darkjune_think」转载请注明。
如果觉得有趣,微信扫一扫关注公众号。
[翻译]AKKA笔记 - 有限状态机 -1的更多相关文章
- AKKA 笔记 - 有限状态机 -2
AKKA 笔记 - 有限状态机 -2 原文地址: http://rerun.me/2016/05/22/akka-notes-finite-state-machines-2/ 在上一节的Akka FS ...
- [翻译]AKKA笔记 -ACTOR SUPERVISION - 8
失败更像是分布式系统的一个特性.因此Akka用一个容忍失败的模型,在你的业务逻辑与失败处理逻辑(supervision逻辑)中间你能有一个清晰的边界.只需要一点点工作,这很赞.这就是我们要讨论的主题. ...
- [翻译] AKKA笔记- ACTORSYSTEM (配置CONFIGURATION 与调度SCHEDULING) - 4(一)
原文在http://rerun.me/2014/10/06/akka-notes-actorsystem-in-progress/ 像我们前面看到的,我们可以用ActorSystem的actorof方 ...
- [翻译]AKKA笔记 - DEATHWATCH -7
当我们说Actor生命周期的时候,我们能看到Actor能被很多种方式停掉(用ActorSystem.stop或ActorContext.stop或发送一个PoisonPill - 也有一个kill和g ...
- [翻译]AKKA笔记 - CHILD ACTORS与ACTORPATH -6
原文:http://rerun.me/2014/10/21/akka-notes-child-actors-and-path/ Actor是完全的继承结构.你创建的任何Actor肯定都是一个其他Act ...
- [翻译]AKKA笔记 - ACTOR生命周期 - 基本 -5
原文地址:http://rerun.me/2014/10/21/akka-notes-actor-lifecycle-basic/ (请注意这了讨论的生命周期并不包括 preRestart 或者pos ...
- [翻译]AKKA笔记 - ACTOR MESSAGING - REQUEST AND RESPONSE -3
上次我们看Actor消息机制,我们看到开火-忘记型消息发出(意思是我们只要发个消息给Actor但是不期望有响应). 技术上来讲, 我们发消息给Actors就是要它的副作用. 这就是这么设计的.除了不响 ...
- [翻译]AKKA笔记 - LOGGING与测试ACTORS -2 (二)
3.THROW IN A LOGBACK.XML 现在我们把SLF4J日志配置在logback. <?xml version="1.0" encoding="UTF ...
- [翻译]AKKA笔记 - LOGGING与测试ACTORS -2 (一)
在前两章 ( 一 , 二 ) ,我们大致讲了Actor和message是怎么工作的,让我们看一下日志和测试我们的 TeacherActor . RECAP 这是上一节我们的Actor代码: class ...
随机推荐
- 【.net 深呼吸】细说CodeDom(4):类型定义
上一篇文章中说了命名空间,你猜猜接下来该说啥.是了,命名空间下面就是类型,知道了如何生成命名空间的定义代码,之后就该学会如何声明类型了. CLR的类型通常有这么几种:类.接口.结构.枚举.委托.是这么 ...
- 前端框架 EasyUI (1)熟悉一下EasyUI
jQuery EasyUI 官方网站 http://www.jeasyui.com/ .去年新开了个中文网 http://www.jeasyui.net/,不知道是不是官方的,不过看着挺像样.但是,广 ...
- Java基础Map接口+Collections工具类
1.Map中我们主要讲两个接口 HashMap 与 LinkedHashMap (1)其中LinkedHashMap是有序的 怎么存怎么取出来 我们讲一下Map的增删改查功能: /* * Ma ...
- Asp.Net MVC中使用StreamReader读取“Post body”之应用场景。
场景:有三个市场(Global.China.USA),对前台传过来的数据有些验证需要细化到每个市场去完成. 所以就出现了基类(Global)和派生类(China.USA) 定义基类(Global)Pe ...
- WPF 有用博客地址
增加智能感知的RichTextBox扩展控件(WPF) WPF自定义控件与样式(3)-TextBox & RichTextBox & PasswordBox样式.水印.Label标签. ...
- 深入理解 Java G1 垃圾收集器--转
原文地址:http://blog.jobbole.com/109170/?utm_source=hao.jobbole.com&utm_medium=relatedArticle 本文首先简单 ...
- Velocity初探小结--velocity使用语法详解
做java开发的朋友一般对JSP是比较熟悉的,大部分人第一次学习开发View层都是使用JSP来进行页面渲染的,我们都知道JSP是可以嵌入java代码的,在远古时代,java程序员甚至在一个jsp页面上 ...
- BPM体系文件管理解决方案分享
一.方案概述 企业管理在很大程度上是通过文件化的形式表现出来,体系文件管理是管理体系存在的基础和证据,是规范企业管理活动和全体人员行为,达到管理目标的管理依据.对与公司质量.环境.职业健康安全等体系有 ...
- Android的Kotlin秘方(I):OnGlobalLayoutListener
春节后,又重新“开张”.各位高手请继续支持.谢谢! 原文标题:Kotlin recipes for Android (I): OnGlobalLayoutListener 原文链接:http://an ...
- GitHub管理代码-随笔
公司一直用的SVN进行项目管理,平时便自己折腾了下Git,这里做下GitHub的最简单的记录... 在git上创建仓库等就免谈了,网上也有好多教程,直接从创建之后记录: 在github的readme文 ...