TL;DR:
状态机框架应该在哪里/如何确定下一个状态?或者说,这实际上是否超出了状态机的范畴,状态机只用于跟踪当前状态并验证请求的转换是否允许呢?
背景和详细信息:
考虑一个简单的杂志文章发布工作流程。以下图表显示了一个基本的、概念性的理解过程,我们想将其转化为代码(在本例中使用Stateless)。它涵盖了出版的基本“正常路径”,以及一些可能出现的问题:
┌───┬──────────────────────────────────────────┐
│ W │ ┌───────────┐ │
│ r │ │ │ │
│ i │ │ Write ◄────────┐ │
│ t │ │ │ │ │
│ e │ └───────────┘ │ │
│ r │ │ │ │
├───┼────────Submit───────────│────────────────┤
│ │ │ │ │
│ │ ┌─────▼─────┐ │ │
│ │ │ │ │ │
│ │ │ Review ◄──────┬─│──────────┐ │
│ │ │ │ │ │ │ │
│ │ └───────────┘ │ │ │ │
│ │ │ │ │ │ │
│ │ ____▼___ │ │ │ │
│ E │ / \ │ │ │ │
│ d │ / Grammar \───No──│─┘ │ │
│ i │ \ OK? / │ │ │
│ t │ \________/ │ Resolve │
│ o │ │ Date │ │
│ r │ Yes Passed │ │
│ │ │ │ │ │
│ │ ____▼___ │ │ │
│ │ / \ │ │ │
│ │ / Libel \────Yes│────────┐ │ │
│ │ \ Risk? / │ │ │ │
│ │ \________/ │ │ │ │
│ │ │ ┌───│───┐ │ │ │
│ │ No │ Defer │ │ │ │
│ │ │ └───▲───┘ │ │ │
│ │ ____▼___ │ │ │ │
│ │ / \ │ │ │ │
│ │ / Embargo? \─Yes───┘ │ │ │
│ │ \ / │ │ │
│ │ \________/ │ │ │
│ │ │ │ │ │
├───┼──────────No────────────────────│───│─────┤
│ L │ │ │ │ │
│ e │ │ ┌─────▼───│─┐ │
│ g │ │ │ Legal │ │
│ a │ │ │ Review │ │
│ l │ │ └───────────┘ │
├───┼──────────│───────────────────────────────┤
│ P │ │ │
│ r │ ┌─────▼───────┐ │
│ i │ │ │ │
│ n │ │ Print it! │ │
│ t │ │ │ │
│ e │ └─────────────┘ │
│ r │ │
└───┴──────────────────────────────────────────┘
我的问题是关于如何考虑状态转换,特别是编辑完成审阅后的转换。
一种方法(A)是让用户选择适当的转换;因此,在这种情况下,编辑器将有单独的按钮,称为Revert to Check Spelling
,Refer for Legal Review
和Defer Publication
。这些按钮将连接到Article
对象上的相应方法,该对象在_stateMachine
上内部调用.Fire(...)
与相应的Triggers
,根据推荐的方法。
然而,这样做至少有两个缺点。首先,它增加了用户的认知负担(在本例中略微增加,但请继续看下去以举例说明)。她不应该选择要做什么,而应该能够在一个表单上一次性完成所有数据输入,如下所示:
┌─────────────────┬──────────────────────┐
│ Spelling OK? │ Yes [ ] No [ ] │
│ Libel Risk? │ Yes [ ] No [ ] │
│ Embargo? │ Date [ dd/mm/yyyy ] │
└─────────────────┴──────────────────────┘
然后应用程序应该决定要做什么。这也可以防止用户做出错误的选择:回答有关内容的事实性问题比决定在可能有许多输入的情况下采取正确的行动更容易(想象一个更复杂的例子)。
第二个缺点是评论可能会被中断:每当发现一个问题并触发相应的操作时,编辑就无法继续评论以潜在地发现其他问题。一旦第一个问题得到解决并且文章再次返回进行评论,它实际上从头开始。
另一个建议(B)是模拟更多状态:一个“语法问题”状态,一个“包含诽谤”的状态等等。然而,这又回到了同样的问题:用户必须单独触发转换到这些状态(假设不是所有这些问题都可以通过例如语法检查器自动确定)。
此外,这感觉像是远离我们相对干净的世界模型,或者至少是远离状态机库所提供的所谓的清洁度。想象一下更复杂的示例中状态的增加,用户输入更多变量。我期待使用Stateless的导出到DOT图功能来实现,正如他们所说,代码是权威来源,图表是副产品。然而,如果输出充满了相当不直观的“状态”,这些状态不再对工作流程的常见理解的“阶段”相对应,则其对利益相关者沟通的价值将会降低。
那似乎让我选择(C),给编辑器一个“提交”函数,其中只包含一堆“if”语句来确定正确的下一个触发器以
.Fire()
。一方面,这感觉像是从状态机框架旨在提供的优势中倒退(这并不是针对Stateless的轻蔑-这个问题的目的是确定我是否持有错误)。另一方面,我认识到,在与其他状态的结构相关联的情况下,我仍然获得了其结构的好处,具有更简单的转换。还有一个简单的ATM示例。如果用户按“确认”并且PIN正确,则进入第2个状态。如果用户按“确认”并且PIN不正确,则进入第3个状态。这个问题似乎更多地涉及建模/符号化而不是实现,但这个例子类似于我编辑器在完成表单后按
Submit
的例子。