状态机:确定下一个状态的多个条件?

5

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 SpellingRefer for Legal ReviewDefer 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的例子。

我相信你帖子中的最后一个问题已经被你使用的Stateless包的设计所回答。它必须解释如何定位转换逻辑和状态逻辑。我不熟悉这个包,所以无法为你指出任何例子。 - user6424206
谁说状态机只能使用二进制决策进行转换?我认为你试图将你的实现与你给用户的接口耦合在一起。你向编辑器呈现数据的方式不必与底层状态机相关。完全可以有一个单独的审查表格,带有一个提交按钮,激活底层机制中的多个转换。你还可以伴随某个转换,比如检测到错误语法,附上必须修复的所有语法错误列表。 - o_weisman
嗨,Ryan。我正在处理一个全新的项目,面临着完全相同的挑战。不介意分享一下你最终是如何解决它的吗? - Francois Botha
1
嗨@FrancoisBotha。我们的解决方案最终看起来与上面的选项A(单独的操作按钮)最相似,但我们通过使用Stateless的Guard Clauses功能避免了用户的认知负荷。因此,用户确实会填写表单,如上所示,然后在没有通用提交按钮的情况下,保护条款确保仅呈现有效的转换选项。因此,我们成功地在状态机库中“本地”处理了逻辑(不知道所有FSM库是否都具有此功能)。希望这有所帮助?对于回复晚了,我们感到抱歉。 - Ryan Jendoubi
1个回答

1
作为建议,我认为您可以考虑在编辑器机器中是否有用自我引用转换。例如,语法状态将具有已标记错误的内部列表,该列表可能为空或非空。不会发生转换,但您可以将其视为无限转换到当前状态。从语法状态转换出现在触发事件(例如提交)时,此时测试内部列表以确定下一个状态。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接