《领域特定语言》一1.2 状态机模型

    xiaoxiao2021-04-16  345

    1.2 状态机模型

    一旦团队达成共识,认为对于指定控制器如何运作而言,状态机是一个恰当的抽象,那么,下一步就是确保这个抽象能够运用到软件自身。如果人们在考虑控制器行为时,也要考虑事件、状态和转换,那么,我们希望这些词汇也可以出现在软件代码里。从本质上说,这就是领域驱动设计(Domain–Driven Design)中的Ubiquitous Language [Evans DDD] 原则,也就是说,我们在领域人员(那些描述建筑安全该如何运作的人)和程序员之间构建的一种共享语言。对于Java程序来说,处理这种事,自然的方式就是以状态机为Domain Model [Fowler PoEAA]。状态机框架的类图见图1-2。

    通过接收事件消息和发送命令消息,控制器得以同设备通信。这些消息都是四字母编码,它们可以通过通信通道进行发送。在控制器代码里,我想用符号名(symbolic name)引用这些消息。我创建了事件类和命令类,它们都有代码(code)和名字(name)。我把它们放到单独的类里(有一个超类),因为在控制器的代码里,它们扮演着不同的角色。

    class AbstractEvent... private String name, code; public AbstractEvent(String name, String code) { this.name = name; this.code = code; } public String getCode() { return code;} public String getName() { return name;} public class Command extends AbstractEvent public class Event extends AbstractEvent

    状态类记录了它会发送的命令及其相应的转换。

    class State... private String name; private List<Command> actions = new ArrayList<Command>(); private Map<String, Transition> transitions = new HashMap<String, Transition>(); class State... public void addTransition(Event event, State targetState) { assert null != targetState; transitions.put(event.getCode(), new Transition(this, event, targetState)); } class Transition... private final State source, target; private final Event trigger; public Transition(State source, Event trigger, State target) { this.source = source; this.target = target; this.trigger = trigger; } public State getSource() {return source;} public State getTarget() {return target;} public Event getTrigger() {return trigger;} public String getEventCode() {return trigger.getCode();}

    状态机保存了其起始状态。

    class StateMachine... private State start; public StateMachine(State start) { this.start = start; }

    这样,从这个状态可以到达状态机里的任何状态。

    class StateMachine... public Collection<State> getStates() { List<State> result = new ArrayList<State>(); collectStates(result, start); return result; } private void collectStates(Collection<State> result, State s) { if (result.contains(s)) return; result.add(s); for (State next : s.getAllTargets()) collectStates(result, next); } class State... Collection<State> getAllTargets() { List<State> result = new ArrayList<State>(); for (Transition t : transitions.values()) result.add(t.getTarget()); return result; }

    为了处理重置事件,我在状态机上保存了一个列表。

    class StateMachine... private List<Event> resetEvents = new ArrayList<Event>(); public void addResetEvents(Event... events) { for (Event e : events) resetEvents.add(e); }

    像这样用一个单独结构处理重置事件并不是必需的。简单地在状态机上声明一些额外的转换,也可以处理这种情况,如下所示:

    class StateMachine... private void addResetEvent_byAddingTransitions(Event e) { for (State s : getStates()) if (!s.hasTransition(e.getCode())) s.addTransition(e, start); }

    我倾向于在状态机上设置显式的重置事件,这样可以更好地表现意图。虽然这样做确实使状态机有点复杂,但它也更加清晰地表现出通用状态机该如何运作,要定义特定状态机也会更加清晰。处理完结构,再来看看行为。事实证明,这真的相当简单。控制器有个handle方法,它以从设备接收到的事件代码为参数。

    class Controller... private State currentState; private StateMachine machine; public CommandChannel getCommandChannel() { return commandsChannel; } private CommandChannel commandsChannel; public void handle(String eventCode) { if (currentState.hasTransition(eventCode)) transitionTo(currentState.targetState(eventCode)); else if (machine.isResetEvent(eventCode)) transitionTo(machine.getStart()); // ignore unknown events } private void transitionTo(State target) { currentState = target; currentState.executeActions(commandsChannel); } class State... public boolean hasTransition(String eventCode) { return transitions.containsKey(eventCode); } public State targetState(String eventCode) { return transitions.get(eventCode).getTarget(); } public void executeActions(CommandChannel commandsChannel) { for (Command c : actions) commandsChannel.send(c.getCode()); } class StateMachine... public boolean isResetEvent(String eventCode) { return resetEventCodes().contains(eventCode); } private List<String> resetEventCodes() { List<String> result = new ArrayList<String>(); for (Event e : resetEvents) result.add(e.getCode()); return result; }

    对于未在状态上注册的事件,它会直接忽略。对于可识别的任何事件,它就会转换为目标状态,并执行这个目标状态上定义 的命令。


    最新回复(0)