状态
本文只讨论计算机里面的状态,并且只是讨论对象,对象其实是抽象的产物,所以状态也取决于我们是如何对对象进行抽象和建模的,根据建模方法不同,对象也不同。对象分为有状态的对象和无状态的对象,无状态对象特指那种特性形态固定不变的对象,他们有些在面向对象领域都是单例的,有些是作为值对象存在的,而有状态对象不同,有状态对象通常是多例的。举个例子
- 有状态对象:Session对象,Session的概念在很多系统中会存在,因为它对每一次会话都会建立该次会话的特征
- 无状态对象:UserFactory对象,作为一个工厂对象,通常就是无状态的,颜色 Red 对象,一种值对象;
以前的老系统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对象,就可以抽象出几种状态:
- 初始状态;
- 思考决策状态;
- 动作执行状态;
- 待用户回复状态;
- 待服务返回状态;
- 结束状态。
现态,后一个状态称为次态。
事件 Event:状态机通常是通过事件驱动运行的,事件代表状态机可以处理的事件,通常发送一件事后,会把事件类型和事件内容一并传递给状态机。状态机接收到事件消息后,调用相应的动作去处理该事件,处理结果可能会是现态到次态的转换。
动作 Action:状态机是如何响应不同的事件的呢,其实每一个状态,都会遇到不同的事件,所以对不同的事件,也是有不同的处理的。比喻在待服务返回状态,发生了待用户说要转换意图的事件,那么事件就是《转换意图》,事件内容是《取消订票》,那么就需要执行 《初始化意图决策流程》这个动作,然后把状态转换为《思考决策状态》。通常事件和状态可以组合为一张表:
事件状态表
属性 | 初始状态 | 思考决策状态 | 动作执行状态 | 待用户回复状态 | 待服务返回状态 | 结束状态 |
新意图事件 | Action1 | Action2 | —— | —— | —— | Action3 |
服务返回事件 | —— | —— | Action2 | Action1 | —— | Action1 |
用户返回事件 | —— | Action1 | —— | —— | Action2 | —— |
服务异常事件 | —— | —— | Action3 | —— | —— | Action3 |
状态转换:介绍完动作和事件后,我们就清楚转换是怎么得到的了,也就是状态 s1 接收到事件 event1 后,执行了某些动作 action1,然后还把状态转换为了 s2,下图是机器人Session对象的状态转换图。
适用范围:我认为状态机适用于状态对象是具有自我协作意识的场景,状态机才会有意义;它使得不同逻辑的代码之间以多态和封装逻辑依赖到各个状态中,从而使得耦合程度最低,易于管理、健壮、维护性好;
具体为(以下场景摘自spring状态机):
- 你可以把你的应用或者应用的一部分用状态做领域表示
- 你需要把复杂的逻辑封装到更小的可以管理的小逻辑
- 对于并发问题,你需要为此写各种处理异步事件先后到达的繁杂的处理逻辑
- 如果你已经开始用一些flag或者枚举去做逻辑规划的话,不如把他们当作状态多态化
- 以上说的flag和变量只是对你的部分逻辑有意义
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两种状态是两个端共有的。
事件:事件即该端口接收到网络的请求,sys\ack\fin等,这里还有事件附带的数据,例如syn =a ,里面的a就是数据,我们一般称之为 payload
具体可参考下图:
状态策略
状态模式是设计模式的内容,但和状态机不态一样。一个类如果有状态,那么其状态的表示是非常多的,而状态模式很多时候就用一个状态类封装其所处的状态。
但有时候你需要的系统的状态是很难穷举的。所以状态模式是一种比较低级的应用了。
无限的状态,有限的Action ——》状态策略:通常,我们的对象建模很难做成状态机,因为状态很可能是无限的,但不管如何,我们的系统执行动作是有限的,也就是State是无限的,而Action是有限的,毕竟Action需要我们程序员手写去实现,那么无限的状态,如果对应这一个个有限的Action呢,我们可以抽象一个根据当前状态选择Action的算法,我们把该算法抽象为<状态策略>注意,这里的策略是Policy,而不是Strategy。在介绍状态策略之前,最好大家先学习一个框架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、保持幂等:可以利用事件的处理幂等,一般事件的处理,在需要更新数据库状态的时候,利用乐观锁保证事务的处理是幂等的,如果更新失败,那么就让事件重试,保证了事件是按照逻辑正确处理的;
-------------------------------- 优秀、是一种习惯 、、、、、、、、、、、、、、、