论坛首页 编程语言技术论坛

强大的有限状态机 - state_machine

浏览 21010 次
该帖已经被评为良好帖
作者 正文
   发表时间:2009-06-21   最后修改:2009-06-21
在推荐Ruby on Rails给做企业应用的朋友时候,比较常见的问题有"Ruby有没有好用的开源工作流引擎?",基于有限状态机(Finite State Machine - FSM)构建工作流引擎是经常使用的实现方法,写篇短文介绍一下Ruby世界里的有限状态机:

首先列几个开源的Ruby FSM项目:

其中第一个项目state_machine是作者经过2年半时间陆续开发的,特性比较完整,而且最近开发也比较活跃,下面简单介绍一下它的强大功能:
  • 可以在任意的Ruby类里面定义多个状态机
  • 基于属性的event transitions
  • 提供了和ActiveRecord / DataMapper / Sequel等多个ORM的集成
  • 提供了before/after transition hook以方便于集成
  • 状态驱动的instance/class行为
  • 基于GraphViz的创建状态机图片
  • ...


1. 基础用法:
我们来看一下它提供的一个红绿灯的例子


得益于Ruby语言的优秀特性,state_machine定义了一套简洁DSL,使得代码非常易懂/易维护:
class TrafficLight
  state_machine :initial => :stop do #定义初始状态为stop
    event :cycle do #定义cycle事件,让状态从stop到proceed到caution到stop进行改变
      transition :stop => :proceed, :proceed => :caution, :caution => :stop
    end
  end
end


使用这个红绿灯的代码:
light = TrafficLight.new
p light.state
6.times do
  light.cycle
  p light.state
end


2. transition hook:
利用state_machine提供的before/after transition hook机制,我们可以简洁地实现很多需求,比如订单状态改变的时候,我们需要记录一下:
class Order
  state_machine :initial => :pending
    before_transition :log_state_change
    #...
  end

  def log_state_change(transition)
    event, from, to = transition.event, transition.from_name, transition.to_name
    puts("Order #{number}: #{from} => #{to} on #{event}")
  end
end


也可以有选择性地在某些关键状态改变时做些动作:
    after_transition  :pending => :completed, :pending => :closed do
      #send email to order owner
    end


3. 和ORM集成:
和Rails的ActiveRecord集成是很简单的一件事情,简单到你什么都不需要做,直接在Model文件里面定义就可以了:
class Order < ActiveRecord::Base
  state_machine :initial => :pending do
    event :confirm do
    #...
    end
  end
end


然后在数据库里面添加一个栏位叫state,这样就可以持久化了:
order = Order.create(...)
p order.state # pending


结合它默认设置的scope,我们就可以写出这样简洁而又符合自然语法的代码:
#找出所有状态为pending的订单,一一进行确认
Order.with_state(:pending).all.each{|order| order.confirm}


和DataMapper / Sequel集成也是很简单,都是直接在Model文件里面定义,然后数据库添加栏位,而不需要额外的工作。

简单介绍到此,state_machine还有更多强大的功能不是这篇短文能够覆盖的,大家有兴趣的可以看它的文档和源代码。

最后值得提一下的是,这个项目总共只有1200行不到的代码,和动辄超过万行的Java workflow engine相比,学习起来是很轻松愉快的。
  • 大小: 20.2 KB
   发表时间:2009-06-23  
状态机做出来的东西,只适合状态较少的情况,而工作流需要能够自由装配节点,根据系统状态自动选择节点,回退,取消,并发节点,已经SPLIT,JOIN等,这些state_machine无能威力.不过我承认,这东西确实很好玩.代码也好懂,就那么几行
0 请登录后投票
   发表时间:2009-06-23  
自动选择节点,这是FSM的基本要求,在state_machine里面通过配置event transition的条件参数(if => proc or function)即可实现。

很多工作流引擎将回退,取消,Loop等作为卖点,对于FSM而言,只是状态改变而已,比如前面提到信号灯的cycle,就是一个Loop,你可以只用写几行代码,将它封装成一个新的DSL。

SPLIT/JOIN有3种不同情况,
1. 状态是同一个,但是由多个角色或者事件处理,实际业务需求中这种情况是最多的,因为它在split或者join的情况下只有单一状态,所以可以用一个FSM搞定的。
2. 状态是同一个,变成了多个子流程,可以用多个定义不同的FSM流程搞定。
3. 状态有多个,这时候FSM就没有办法了,可以考虑用Petri Net,不过这种情况在企业应用中遇到的最少。

很多工作流引擎号称有这个特性,有那个卖点,但是对于大部分的企业应用来说,其实还不如一个简洁的状态机好用。
0 请登录后投票
   发表时间:2009-06-23  
QuakeWang 写道
自动选择节点,这是FSM的基本要求,在state_machine里面通过配置event transition的条件参数(if => proc or function)即可实现。

很多工作流引擎将回退,取消,Loop等作为卖点,对于FSM而言,只是状态改变而已,比如前面提到信号灯的cycle,就是一个Loop,你可以只用写几行代码,将它封装成一个新的DSL。

SPLIT/JOIN有3种不同情况,
1. 状态是同一个,但是由多个角色或者事件处理,实际业务需求中这种情况是最多的,因为它在split或者join的情况下只有单一状态,所以可以用一个FSM搞定的。
2. 状态是同一个,变成了多个子流程,可以用多个定义不同的FSM流程搞定。
3. 状态有多个,这时候FSM就没有办法了,可以考虑用Petri Net,不过这种情况在企业应用中遇到的最少。

很多工作流引擎号称有这个特性,有那个卖点,但是对于大部分的企业应用来说,其实还不如一个简洁的状态机好用。


还有几个:
1.状态机应该是依附在某个model上的,而工作流很多时候不是依附于model的.
2.如果状态过多,写成一团是一件相当可怕的事情.
3.节点之间可以自由组合,这个状态机能做否?
0 请登录后投票
   发表时间:2009-06-23   最后修改:2009-06-23
在补充一个,因为我工作流刚入门,不一定说的对.但是,状态是多个的应该也很常见吧.
比如:为了请亲朋好友吃饭,我需要
1.打电话给饭店订座位.
2.打电话给亲朋好友.
3.去银行取钱.
这三个步骤是并行的,或者说,他们的执行没有先后顺序(你就当他们没有...).这个时候,状态机有办法做吗?

---------------------------
再补充,一些workflow,对于每个状态的改变,都要记录,这个有办法实现否?
还有一些,大部分工作流中任务的分配是根据角色来做的,而state machine没有角色信息.那么下一个任务发给谁做,这个该怎么做?

抱歉问这个多,只是最近正在做一个工作流,所以想请教一下咯咯
0 请登录后投票
   发表时间:2009-06-23   最后修改:2009-06-24
状态过多,不是问题 ……
用 namespace 即可 ……





似乎不支持平行 ……
修改了一下。(今天好热)
require 'state_machine'
class StateMachine::Machine
  def par_event events, test_field, trans
    i = 1
    # binary 11....1
    full = (1 << events.size) - 1
    events.each do |e|
      j = i
      event e do
        transition(hash.merge(:if => (lambda{ |obj|
          # if all events are fired
          # then test_field == full
          # then transition will be done
          new_tf = ((obj.send(test_field) || 0) | j) # new test field
          obj.send "#{test_field}=", new_tf
          full == new_tf
        })))
      end
      i = i << 1
    end
  end
end


用法:
class Bus
  # helper field
  attr_accessor :parallel_state
  state_machine :initial => :stay do 
    par_event [:drive,:inject_gas], :parallel_state, :alive =>:run
  end
end
b = Bus.new
b.inject_gas # => false
b.drive # => true




似乎不支持记录,但是很难么……
假设你的字段是 state,那么对应起来就像这样:
state_machine :state do
  ...
end

如何记录状态转换? 假设你有个 Logger
def recorded_state= s
  Logger.log "#{self.id}: #{self.state} to #{s}"
  self.state = s
end
state_machine :recorded_state do
  ...
end



貌似本段不合需求,已删除 ……


ps:本人没有接触过 state_machine,没有接触过工作流,以上代码随便写写,不一定完全正确,看看即可 ……
工作流什么的,你没写 model,它也有 model,只是它的代码随随便便就几万行,一般人懒得吐槽而已 ……
另外对楼上说的自由结合什么的理解不能,所以也没法 google 出答案 ……
  • 大小: 27.4 KB
0 请登录后投票
   发表时间:2009-06-23  
其他的不说,那个角色信息是只每个状态都有自己所属的角色,每个用户登陆系统后应该有自己的工作列表……

还有,自动节点怎么处理?
0 请登录后投票
   发表时间:2009-06-24   最后修改:2009-06-24
你这种 role 要求,增加一个字段 state_role 保存 state 元数据就行了吧。

自动节点 …… 想要应该可以这样:
event :auto do
  transition :a => :b
  transition :b => :c
  transition :c => :d
end


我觉得状态机是用简单的规则去完成复杂的问题,扩展性还是挺好的。
0 请登录后投票
   发表时间:2009-06-24  
event :auto是指系统自动运行的吗?如果不是,那可不能算是自动节点.还有,你的并行的例子不大好懂...
0 请登录后投票
   发表时间:2009-06-24   最后修改:2009-06-24
翻了我06年写过的一篇博客:
http://blog.donews.com/liusong1111/archive/2006/09/12/1035673.aspx
liusong1111 写道
一句话之workflow

serializable,continuable,traceable CodeSegment
算法作为数据,用管理数据的方式 管理算法,就是所谓的工作流
singleton workflow entity,用ruby的singleton_class/metaclass实现FSM式工作流



工作流用来解决什么问题?我现在的看法跟那时差不多。

工作流是采用管理数据流的方式 来管理控制流,试图解决常规执行序列不具有的功能:
1. Serializable:状态可随时序列化。类比游戏中可以随时存盘、读盘。
2. Continueable:人机交互 不破坏流程表达和执行的完整性。参考ruby的continuation、java的RIFE框架。
3. Traceable:   执行过程的历史记录被保存、版本化。可用于跟踪、统计等。

一句话,工作流是用来解决状态管理问题的,兼顾控制流的建模。

因为,对控制流建模的支持,象before hook、event driven,可以在编程语言、通用类库层面去做,不是工作流引擎的专利,也不构成它的核心价值。

如果能完美处理状态,工作流就没有存在的必要。问题是目前做不到,而且对状态的管理工作流引擎也只能帮一部份,还有大量工作需要开发者去做,悲惨世界。造成厂商宣传它的功能时往往拿后者(对控制流建模)说话。

从这个角度看,一个执行序列本来就有多个状态。
所谓的split/join就是多线程(共享状态)的模型。
角色解析是个跟工作流本质关系不大,却经常在一块的东西。工作项管理也差不多,作用在于将工作项持久化了。
这两个功能通常作为工作流引擎的外接模块。

state_machine对控制流建模提供了完善的支持,而把状态的维护完全推给了开发者,自己只维持内存中的模型。
3 请登录后投票
论坛首页 编程语言技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics