在这里回答问题的后半部分,关于如何到达 Q4 和 Q5(暂时忽略 Q1 到 Q6)
将其表示为状态机的典型第一种天真方式(也是我最初的方式)使每个问题进入其自己的状态,每个状态在状态机中仅表示一次。使用状态图,您可以将问题 4 和 5 提取为复合状态,这样当 Q4AND5 处于活动状态时,Q4 或 Q5 中的一个正好处于活动状态:
<scxml>
<state id="Q1"/>
<state id="Q2"/>
<state id="Q3"/>
<state id="Q4AND5">
<state id="Q4"/>
<state id="Q5"/>
</state>
<state id="Q6"/>
</scxml>
然后从 Q3 到 Q4AND5 的转换将导致 Q4 或 Q5 变为活动状态,因为有保护的转换。
<scxml>
<state id="Q1">
<transition event="answer" target="Q2"/>
</state>
<state id="Q2">
<transition event="answer" target="Q3"/>
</state>
<state id="Q3">
<transition event="answer" target="Q4AND5"/>
</state>
<state id="Q4AND5">
<transition cond="if q1 == 'FOO' and q3 == 'BAR'" target="Q4"/>
<transition cond="if q1 == 'BAR' and q3 == 'FOO'" target="Q5"/>
<state id="Q4"/>
<state id="Q5"/>
<transition event="answer" target="Q6"/>
</state>
<state id="Q6"/>
</scxml>
从 Q6返回将转到 Q4AND5,这将导致机器进入 Q4 或 Q5 :
<state id="Q6">
<transition event="back" target="Q4AND5"/>
</state>
现在,在修改问题以包含 Q1 到 Q6 的转换之后,很明显将每个问题建模为不同的状态并不能解决问题。这也不完全正确。想想看,Q6 有两种状态,一种是从 Q1 到达 Q6 之后,另一种是从 Q4AND5 到达 Q6 之后。如果我们将这两个 Q6 拆分为两个不同的状态,那么很容易适应这种新的过渡:
<scxml>
<state id="Q1">
<transition event="answer" target="Q6B" cond="q1 == 'BAZ'"/>
...
</state>
...
<state id="Q6A">
<transition event="back" target="Q4AND5"/>
</state>
<state id="Q6B">
<transition event="back" target="Q1"/>
</state>
</scxml>
现在的问题是 Q6 由两个状态(Q6A 和 Q6B)表示。这里的解决方案是从状态本身的名称中分离出来,并在每个状态中声明要显示哪个问题,通常通过一个操作或更改某个变量的方式。下面我定义了一个数据元素,状态图更新为要显示的正确问题的名称。
<scxml>
<datamodel>
<data id="question"> <!-- The name of the question to show -->
</datamodel>
<state id="Q1">
<assign location="question" expr="'Q1'"/>
<transition event="answer" target="Q6B" cond="q1 == 'BAZ'"/>
<transition event="answer" target="Q2"/>
</state>
<state id="Q2">
<assign location="question" expr="'Q2'"/>
<transition event="answer" target="Q3"/>
<transition event="back" target="Q1"/>
</state>
<state id="Q3">
<assign location="question" expr="'Q3'"/>
<transition event="answer" target="Q4AND5"/>
<transition event="back" target="Q2"/>
</state>
<state id="Q4AND5">
<transition event="answer" cond="if q1 == 'FOO' and q3 == 'BAR'" target="Q4"/>
<transition event="answer" cond="if q1 == 'BAR' and q3 == 'FOO'" target="Q5"/>
<transition event="back" target="Q3"/>
<state id="Q4">
<assign location="question" expr="'Q4'"/>
</state>
<state id="Q5">
<assign location="question" expr="'Q5'"/>
</state>
<transition event="answer" target="Q6A"/>
</state>
<state id="Q6A">
<assign location="question" expr="'Q6'"/>
<transition event="back" target="Q4AND5"/>
</state>
<state id="Q6B">
<assign location="question" expr="'Q6'"/>
<transition event="back" target="Q1"/>
</state>
</scxml>
这种解耦使更改状态图变得更容易,在不改变状态图的每个用户的情况下移动事物。相反:依赖于状态图/状态机之外的状态名称会导致状态名称本身成为状态机的 API,这意味着它不能轻易更改。