【状态管理】状态机模型和状态管理器模型

状态

  本文只讨论计算机里面的状态,并且只是讨论对象,对象其实是抽象的产物,所以状态也取决于我们是如何对对象进行抽象和建模的,根据建模方法不同,对象也不同。对象分为有状态的对象和无状态的对象,无状态对象特指那种特性形态固定不变的对象,通常他们在面向对象领域都是单例的,而有状态对象不同,有状态对象通常是多例的。举个例子

  • 有状态对象:Session对象,Session的概念在很多系统中会存在,因为它对每一次会话都会建立该次会话的特征
  • 无状态对象:UserFactory对象,作为一个工厂对象,通常就是无状态的

  以前的老系统EJB,有激活和钝化的概念,但现在的编程系统都基本自己负责实现有状态对象的存储了,DDD中有一个概念就是rebuild模式,就是对象的重建。简单的状态是不需要关注的,但是如果要根据状态进行驱动,那么就要对状态进行管理,下面介绍几种状态驱动的模型,第一种是普通的状态,第二种是状态机,第三种是有限Action的状态管理器

普通枚举状态

  这种方式的状态是最简单的对象状态表示,我们时不时都会用上,例如你会在一个实例类中加一个字段:boolean isFinished ;用作表示对象是否已经是完成状态,或者:boolean isRunning;用做表示对象是否在运行状态,这种状态一般都是可枚举完的,而且不像状态模式是状态之间的扭转,而是由调用者设置属性状态,例如 obj.setFinished ;

  注意,你可以不必拘泥于一种方式组织枚举状态,你也可以用一个栈组织这些状态,例如:

/**
 *
 * 代表一个对话管理的目标状态
 *
 **/
private Stack<Goal> interGoal = new Stack();

public void addGoal (Goal newGoal){
    interGoal.push(newGoal);  
}

public void doGoal (){
    Goal currentGoal =  interGoal.peek();
    currentGoal.doGoal;
    interGoal.poll;
}

  如上,对象的状态是对话目标,对话目标可以用栈来表示,这样的好处是:状态可以记录,回退;所以状态的表示,只需要你巧妙利用数据结构,就能达到一些不可思议的效果。

FMS 有限状态机

  状态机是相对一个系统而言的,其实准确来说,是一个比较核心的对象而言,这个对象的职责和功能,是通过状态机这种数学模型建模得以运作。例如我们的聚合根对象。

  状态机,很多文章介绍会说直接等同于有限状态机(FMS),FSM 解决一个输入序列,经过 FSM,最终停留在什么状态这样一个问题。比较注重的是系统(对象)的状态,和状态之间的转换,状态是历史输入的一种结果,对于一个系统而言,状态机基本包含以下几种模型:

  • 事件(Event)
  • 动作(Action)
  • 状态(State):现态,次态
  • 转换

  状态 State:状态机的状态是有限的,例如一个机器人对话系统的Session对象,就可以抽象出几种状态:

  • 初始状态;
  • 思考决策状态;
  • 动作执行状态;
  • 待用户回复状态;
  • 待服务返回状态;
  • 结束状态。

  而当前时刻,Session只能处于一个状态中;状态转换前后,前一个状态称为现态,后一个状态称为次态

  事件 Event:状态机通常是通过事件驱动运行的,事件代表状态机可以处理的事件,通常发送一件事后,会把事件类型和事件内容一并传递给状态机。状态机接收到事件消息后,调用相应的动作去处理该事件,处理结果可能会是现态到次态的转换

  动作 Action:状态机是如何响应不同的事件的呢,其实每一个状态,都会遇到不同的事件,所以对不同的事件,也是有不同的处理的。比喻在待服务返回状态,发生了待用户说要转换意图的事件,那么事件就是《转换意图》,事件内容是《取消订票》,那么就需要执行 《初始化意图决策流程》这个动作,然后把状态转换为《思考决策状态》。通常事件和状态可以组合为一张表:

事件状态表
属性  初始状态 思考决策状态 动作执行状态 待用户回复状态 待服务返回状态 结束状态
新意图事件 Action1 Action2  ——  —— ——   Action3
服务返回事件  ——  —— Action2  Action1  ——  Action1
用户返回事件 ——  Action1  ——  ——  Action2  ——
服务异常事件  ——  ——  Action3  ——  ——  Action3

   状态转换:介绍完动作和事件后,我们就清楚转换是怎么得到的了,也就是状态 s1 接收到事件 event1 后,执行了某些动作 action1,然后还把状态转换为了 s2,下图是机器人Session对象的状态转换图。  

TCP 连接协议状态

  下面给出一个TCP协议的有限状体机的示例,该例子估计大家都看得明白,Client客户端和Server服务端,都对应有自己的状态State和动作Action,分别对应着事件Event和转换

  状态:closed、syn_sent、established、fin_wait1、fin_wait2、time_wait、listen、syn_received、close_wait、last_act

  以上状态区别,一般还按照自身是发起端还是主动关闭端区分,只有close和established两种状态是两个端共有的。

  事件:事件即该端口接收到网络的请求,sysackfin等,这里还有事件附带的数据,例如syn =a ,里面的a就是数据,我们一般称之为 payload

  动作和状态转换关系具体可参考下图:

状态策略

  状态模式是设计模式的内容,但和状态机不态一样。一个类如果有状态,那么其状态的表示是非常多的,而状态模式很多时候就用一个状态类代表其所处的状态。有时候你需要的系统的状态是很难穷举的。所以状态模式是一种比较低级的应用了。

  无限的状态,有限的Action ——》状态策略:通常,我们的对象建模很难做成状态机,因为状态很可能是无限的,但不管如何,我们的系统执行动作是有限的,也就是State是无限的,而Action是有限的,毕竟Action需要我们程序员手写去实现,那么无限的状态,如果对应这一个个有限的Action呢,我们可以抽象一个根据当前状态选择Action的算法,我们把该算法抽象为<状态策略>。在介绍状态策略之前,最好大家先学习一个框架Redux

  Redux:首先介绍下Redux,前端框架的一种,这个自称是状态机实现,每一个组件都是状态机,这个是我认为最合适作为状态机定义的实现,因为React接受外界输入action后,会根据state重新渲染组件,所以会带有观察者模式的感觉,所以我认为它更接近状态机的本质 —— 状态驱动

  下面一段redux的小demo,代表整个redux的工作原理

function createStore(reducer, preloadedState) {
    let state = preloadedState
    let listeners = []

    function subscribe(listener) {
        listeners.push(listener)
    }

    function getState() {
        return state
    }

    function dispatch(action) {
        state = reducer(state, action)
        for (listener of listeners) {
            listener()
        }
    }
    /**通过一个不匹配任何 reducer的 type,来全部的初始值*/
    /**redux 中是使用一个随机的字符串来保证不匹配任何 reducer, 这里使用了 ES6 的 Symbol 类型*/
    dispatch({ type: Symbol() });

    return {
        subscribe,
        getState,
        dispatch
    }
}

  策略:Policy,是一种Action选择的算法实现,它的输入是当前状态,输出是动作 Action,策略可以用规则实现,也可以用机器学习等算法实现。我们现场定义一个策略的接口

public class Policy{
    // 根据当前状态,选择处理这个状态的策略
    Action chooseAction(State state);  

}

  有了接口,我们就不用担心具体的实现,只需要执行Action即可。而Action的执行,当然是交给状态管理器了。随着状态管理器执行完Action,Action会改变状态,也会出发观察者,所有外界就得到通知,外界处理完后会回馈到系统中,系统收到反馈后,这里注意的是,Action会收到这种反馈,然后改变状态State,当State改变后,系统可以把新的State传递给Policy,然后继续这个良性循环,从而整个状态机,得到了驱动。

  状态管理器:Redux就是一个状态管理器,但是比较无奈的是,它接受的输入是外界的Action,使得状态改变,然后通知所有状态的观察者,这种模式的实现,也只有前端比较合适应用了,下面我给大家再介绍一个例子,那就是对话系统的例子,DST(Dialog State Tracker)和DP(Dialog Policy),前者为对话状态跟踪,后者为对话策略,其中对话策略可以用机器学习算法实现;

状态机与线程安全

  状态模式,一般情况下,都是一件事一件事发生和处理的,但是有时候我们需要处理的系统并非如此简单,而是大量的事件在不同的事件发生,这个时候,我们可以有以下几种建模方式:

  1、单线程:这种最简单,一个状态机维护一个事件队列,所有事件按照时间的顺序入队,然后用单一的线程处理该队列的事件,这样可以保证每个事件的事务性,但缺点也是有的,处理速度会变得更慢,而且会导致无事件的时候,线程闲置,优点是简单,而且可以给事件作优先级队列,使得重要的事件可以优先处理。

  2、多状态:如果事件数量较少,我们可以把事件的先来后到的所有组合情况的状态都抽象出来,每一个事件的组合发生顺序都有一个对应的状态,优点是事件可以多线程,但缺点是状态会过多,难维护,所以适合事件数量较少的情况,最好是两三个事件。

  3、保持幂等:状态按照业务需求设计,让状态机可以多线程运作事件,但也并非没有同步机制,可以用状态的改变的原子性同步,会有个缺点就是可能会在状态切换的时候发生ABA问题,但ABA也可以理解的,毕竟事件都有权利按照自己的事务执行,所以在action的设计中,要特别的注意相关所有状态对象的幂等处理,保证同一个消息被状态机处理多次,效果是相同的,也就是包装action独立于事件即可。

原文地址:https://www.cnblogs.com/iCanhua/p/11444420.html