原文地址 译者:Zhanggc
建立有限状态机角色
概述
有限状态机模式在Erlang design principles里面被很好描述出来.简而言之,它可以被视为一组关系:
State(S) x Event(E) -> Actions (A), State(S’)
这些关系描述为:
如果我们在状态S 和 时间E 发生,我们应该执行动作A 与转换到状态S’.
而Scala 程序语言使构建一个良好内部DSL(领域特定语言)成为可能,后者用于规划有限状态机(请见FSM)。对于用同样方法,由于Java语法冗长不适合构建。本章节介绍通过自身训练效实现相同关注点分离方法。
状态应该如何处理
FSM角色实现引用的所有可变字段(或可传递的可变数据结构)应该被收集在一个地方及仅通过使用小粒度的的定义良好的一组方法来改变。要做到这一点的一种实现方法是集合所有可变状态在一个超类中,并且保持其的私有及为改变其提供保护方法。
import java.util.ArrayList;
import java.util.List;
import akka.actor.ActorRef;
public abstract class MyFSMBase extends UntypedActor {
/*
* This is the mutable state of this state machine.
*/
protected enum State {
IDLE, ACTIVE;
}
private State state = State.IDLE;
private ActorRef target;
private List<Object> queue;
/*
* Then come all the mutator methods:
*/
protected void init(ActorRef target) {
this.target = target;
queue = new ArrayList<Object>();
}
protected void setState(State s) {
if (state != s) {
transition(state, s);
state = s;
}
}
protected void enqueue(Object o) {
if (queue != null)
queue.add(o);
}
protected List<Object> drainQueue() {
final List<Object> q = queue;
if (q == null)
throw new IllegalStateException("drainQueue(): not yet initialized");
queue = new ArrayList<Object>();
return q;
}
/*
* Here are the interrogation methods:
*/
protected boolean isInitialized() {
return target != null;
}
protected State getState() {
return state;
}
protected ActorRef getTarget() {
if (target == null)
throw new IllegalStateException("getTarget(): not yet initialized");
return target;
}
/*
* And finally the callbacks (only one in this example: react to state change)
*/
abstract protected void transition(State old, State next);
}
上面方法好处是状态改变可以表现在一个中心位置之上,当添加到FSM机器时,这使得它不可能忘记插入代码对于状态转变的反应。
消息集束器的例子
上面显示的基类被设计支持一个类似例子作为Scala FSM 文档:一个接收和排队消息的角色,分批交付给一个可配置的目标角色。涉及的消息是:
public final class SetTarget {
final ActorRef ref;
public SetTarget(ActorRef ref) {
this.ref = ref;
}
}
public final class Queue {
final Object o;
public Queue(Object o) {
this.o = o;
}
}
public final Object flush = new Object();
public final class Batch {
final List<Object> objects;
public Batch(List<Object> objects) {
this.objects = objects;
}
}
该角色仅有两个状态 IDLE 和 ACTIVE,使他们处理相当直接在来自于基类的具体角色。
import akka.event.LoggingAdapter;
import akka.event.Logging;
import akka.actor.UntypedActor;
public class MyFSM extends MyFSMBase {
private final LoggingAdapter log =
Logging.getLogger(getContext().system(), this);
@Override
public void onReceive(Object o) {
if (getState() == State.IDLE) {
if (o instanceof SetTarget)
init(((SetTarget) o).ref);
else
whenUnhandled(o);
} else if (getState() == State.ACTIVE) {
if (o == flush)
setState(State.IDLE);
else
whenUnhandled(o);
}
}
@Override
public void transition(State old, State next) {
if (old == State.ACTIVE) {
getTarget().tell(new Batch(drainQueue()), getSelf());
}
}
private void whenUnhandled(Object o) {
if (o instanceof Queue && isInitialized()) {
enqueue(((Queue) o).o);
setState(State.ACTIVE);
} else {
log.warning("received unknown message {} in state {}", o, getState());
}
}
}
这里技巧是分解出像whenUnhandled 与transition 这样的基本功能,以便获得一些定义良好方面在对于改变或插入记录做出的反应。
状态中心 VS 事件中心
在上面例子中,状态和事件的主观复杂性是大致相等的,使得它看起来是是否选择主派发的问题;在这个例子中基于状态的派发器被选中。取决于如何均衡构建状态与事件可行模型,首先处理不同事件,接着辨别状态,这中做法可能是更实际。一个例子是一个状态机,它具有多种内部状态,但只处理很少不同事件。