从工作流状态机实践中总结状态模式使用心得

03-12-07 banq
              

状态模式好像是很简单的模式,正因为状态好像是个简单的对象,想复杂化实现设计模式就不是容易,误用情况很多。

我个人曾经设计过一个大型游戏系统的游戏状态机,游戏状态可以说是游戏设计的主要架构,但是由于系统过分复杂

和时间仓促,并没有真正实现状态模式。

目前在实现一个电子政务项目中,需要进行流程状态变化,在电子政务设计中,我发现,如果一开始完全按照工作流

规范开发,难度很大,它和具体项目实践结合无法把握,而且工作流规范现在有wfmc,还有bpml,选择也比较难。因

此,我决定走自创的中间道路。

因为,我需要做一个状态机API,或者说状态机框架,供具体系统调用:类如公文流转应用或信息发报送应用等。

好的状态模式必须做到两点:

1. 状态变化必须从外界其它逻辑划分出来。

2. 状态必须可方便拓展,对其它代码影响非常小。

要做到这两点,必须先明确状态变化机制,状态变化实际是由Event事件驱动的,可以认为是Event-condition-State,

在MVC模式一般是Event-condition-Action实现。状态模式需要封装的是Event-condition-State中的condition-State

部分。

清晰理解状态和流程的关系也非常重要,因为状态不是孤立的,可以说和流程是点和线的关系,状态从一个方面说明

了流程,流程是随时间而改变,状态是截取流程某个时间片。因此,必须明白使用状态模式实现状态机实际是为了更

好地表达和说明流程。

状态和流程以及事件的关系如下:

                 |Event
___currentState__|______newState___
<p>

图中表示了是事件改变了流程的状态,在业务逻辑中,经常发生的是事件,如果不使用状态模式,需要在很多业务逻

辑处实现事件到状态判定和转换,这有很多危险性。

最大的危险是系统没有一个一抓就灵的主体结构,以那个游戏系统为例,在没有状态模式对状态提炼的情况下,状态

改变由每个程序员想当然实现,导致每个程序员开发的功能在整合时就无法调试,因为这个程序员可能不知道那个程

序员的代码在什么运行条件下改变了游戏状态,结果导致自己的代码无法运行。

这种现象实际上拒绝了项目管理的协作性,大大地拖延项目进度(程序员之间要反复商量讨论对方代码设计)。从这

一点也说明,一个好的架构设计是一个项目快速成功完成的基础技术保证,没有这个技术基础,再先进的项目管理手

段也是没有效率的,或者是笨拙的。

状态模式对于很多系统来说,确实是架构组成一个重要部分。

下面继续讨论如何实现一个好的状态模式,为了实现好的状态模式,必须在状态模式中封装下面两个部分:

1. 状态转换规则(行为)

2. 状态属性(属性)

状态转换行为有两种划分标准:

1. run和next两个行为,run是当前状态运行行为,next是指在Event参与下,几种可能转向的下一个状态。

2. stateEnter和stateExit, 状态进入和状态退出。

如果用进入一个个房间来表示状态流程的话, 第一种分析是只重视着“在房间里”和“如何转入下一个房间”,这两种行

为一旦确定,可以被反复使用,进而一个流程的状态切换可以全部表达出来。

第二中分析方法有所区别,只重视进入房间和离开房间这个两个行为,同样,这种模型也可以被反复利用在其它房间,

一个流程的状态切换也可以全部表达出来。

具体选择取决于你的需求,比如,如果你在进入一个状态开始,要做很多初始化工作,那么第二种模型就很适合。

状态变化都离不开一个主体对象,主体对象可以说包含状态变化(行为)和状态属性(属性),假设为StateOwner,

StateOwner有两个部分组成:Task/事情和状态。任何一个Task/事情都会对应一个状态。

这样,我们已经抽象出两个主要对象:状态State和StateOwner。

为了封装状态变化细节,我们可以抽象出一个状态机StateMachine来专门实现状态根据事情实现转换。

这样,客户端外界通过状态机可访问状态模式这个匣子。在实践中,外界客户端需要和状态机实现数据交换,我们把

它也分为两种:属性和行为。

其中属性可能需要外界告诉状态状态变化的主体对象Task,解决状态的主人问题,是谁的问题;行为可能是需要持久

化当前这个主体对象的状态到数据库。

这两种数据交换可以分别通过StateOwner和StateMachine与整个状态机实现数据交换,这样,具体状态和状态切换也

和外界实现了解耦隔离。

因此好的状态模式实现必须有下列步骤:

(1)将每个状态变成State的子类,实现每个状态对象化。

(2)在每个状态中,封装着进入下一个状态可能规则,这些规则是状态变化的核心,换句话说,统一了状态转换的规则。

具体可采取run和next这样的转换行为。

下面是一个子状态代码:


public class Running extends StateT{

  //
  public void run(StateOwner stateOwner){
     stateOwner.setCurrentState(this);
  }

  //转换到下一个状态的规则
  //当前是Running状态,下一个状态可能是暂停、结束或者强制退出等
  //状态,但是绝对不会是Not_Started这样的状态
  //转换规则在这里得到了体现。
  public State next(Event e) {
    if(transitions == null){
       addEventState(new EventImp("PAUSE"), new Suspended());
       addEventState(new EventImp("END"), new Completed());
       addEventState(new EventImp("STOP"), new Aborted());
    }
    return super.next(e);
  }

}     



外界直接调用 StateMachine的关键方法transition;实行状态的自动转变。  


public class StateMachine {

 /**
   * 状态切换
   * 根据Event参数,运行相应的状态。
   * 1. 获得当前状态
   * 2. 使用当前状态的next()转换
   *                  |Event
   * ___currentState__|______newState___
   *
   * @param inputs
   */
  public final void transition(String taskid, Event e) throws Exception {
    State currentState = readCurrentState(taskid); //从数据库获得当前状态
    StateOwner stateOwner = new StateOwner(taskid, currentState);
    //转换状态
    currentState = currentState.next(e);
    if (currentState != null) {
      currentState.run(stateOwner);
      saveCurrentState(stateOwner); //保存当前状态
    }
  }

} 

              

4
iceant
2003-12-08 11:16

我原来的想法是使用 Event 为中心的处理机制: Event + Listener

拿你说的从一个房间进入另一个房间来说:

临界事件,就是跨过门。所以,在系统中,一般会有三个处理方法:

prePassThroughDoor();

doPassThroughDoor();

postPassThroughDoor();

在每个事件,都可以加入相应的 Listener. 这样可以做到系统的可扩展性。

但是这有个问题,就是所有的事件都必须去手写实现,这会很麻烦。

现在有 AspectJ, 如果我对 AspectJ 的理解没错的话,它很容易就可以实现这一点。你可以在任一方法调用之前,或之后,加入相应的事件点,在事件点上加入 Listener 机制。

chenye99
2003-12-08 11:34

> public class Running extends StateT{

>

> //

> public void run(StateOwner stateOwner){

> stateOwner.setCurrentState(this);

> }

>

> //转换到下一个状态的规则

>

> //当前是Running状态,下一个状态可能是暂停、结束或者强

> 仆顺龅?> //状态,但是绝对不会是Not_Started这样的状态

> //转换规则在这里得到了体现。

> public State next(Event e) {

> if(transitions == null){

> addEventState(new EventImp("PAUSE"), new

> ), new Suspended());

> addEventState(new EventImp("END"), new

> ), new Completed());

> addEventState(new EventImp("STOP"), new

> ), new Aborted());

> }

> return super.next(e);

> }

>

> }

>

>

>

> 外界直接调用

> StateMachine的关键方法transition;实行状态的自动转变。

对于return super.next(e)的处理方式不是很清楚,可否给出StateT的next方法的代码?

banq
2003-12-08 18:00

StateT的next方法如下:

 public State next(Event e) throws InvalidStateException{
    logger.debug(" transitions size = " + transitions.size());
    String name = e.getName();
    if (transitions.containsKey(name))
      return (State) transitions.get(name);
    else
      throw new RuntimeException("Input not supported for current state "
                                 + " state is " + this.getClass().getName()
                                 + " Event is " + e.getName() );
  }

chenye99
2003-12-09 11:45

我理解Suspended、Completed、Aborted类也都是StateT类的子类,不知是不是这样?

在StateT类的next方法中,是根据事件来进行跳转,假设可能又两个状态都存在PAUSE事件,又怎么区分呢?

在刚看到方案时我的感觉是StateT子类中next方法应为return super.next(this,e),不知是不是我的理解有问题?

7Go 1 2 3 4 ... 7 下一页