繁体   English   中英

如何正确使用状态模式?

[英]How to use state pattern correctly?

在我的编程经验中,我遇到过一些状态模式的实现,并做了一些。 我见过它们用于各种场景(主要是 UI 和解析)。 麻烦的是,在快速开发的压力下,它们都变成了难以维护和理解的代码块。 我正在考虑重构其中的一个,但我很难在网上找到好的资源。 网上有很多状态模式的简单示例,但我需要一些更深入的资源。

所以我在寻找:

  • 实施状态模式时常见陷阱的示例以及如何避免它们,
  • 正确完成状态模式的真实示例(例如在某些开源项目/框架中)
  • 也欢迎使用状态模式的个人经验

感谢您的时间

@Ivan:网络上有许多可用于分层状态机(HSM) 的资源。 Miro Samek 撰写了大量有关此设计模式的文章,并提供了大量有用的信息。

一些应该感兴趣的文章:

与 Mealy 和 Moore 描述的平面 FSM 状态图相比,使用 HSM 的最大好处是层次结构创建了责任分离。 子状态只需要处理它们明确设计用来处理的那些条件——未处理的事件向上传递给父状态,如果父状态没有明确设计用来处理它,那么它将向上传递给下一个更高的父母等等。 它允许您创建小型的、易于管理的状态机,每个状态机都服务于一个单一的目的——一个可以适合单个对象的状态机。 随着新功能的添加或新类的添加,他们只需要处理自己的一小部分世界并将未处理的事件传递给各自的父级。

正确实施后,您将获得一个圈复杂度低的健壮程序,该程序易于根据需要修改或升级。

正如您可能已经读过的那样,当状态改变某些对象的行为时,状态设计模式很有用,而该对象的组成包括该状态。 这意味着State抽象类、接口或枚举类型的想法——尽管取决于Duck Typing也将执行的语言——它定义了任何常见的行为和/或所需的方法。

关键方面

在使用状态模式时,确实有两个关键方面需要考虑:枚举和转换。 枚举只是意味着识别一组可能的状态(例如一周中的几天),或者更抽象地识别状态类型(即元状态),例如工作流引擎的开始、结束和中间状态。转换意味着决定如何对运动建模状态之间通常通过捕获表格表示中的所有可能转换(即有限状态机)或使每个状态知道其到其他状态的可能“转换”来完成。

通常,转换与元状态密切相关,因为在这样一个可以在运行时添加新状态和转换的动态系统中,不可能提前知道所有状态和关系。 此外,通过过渡方法,某些行为(例如通知)成为过渡的一部分,而不是状态本身。

例子

我已经处理或讨论了几种使用设施的场景:

  1. 工作流程
  2. 电脑游戏对手AI
  3. 流程编排

我所说的工作流是指类似jBPM的东西。 像这样的系统关注的是在正确的时间吸引正确的人的正确注意力。 他们通常会发送大量电子邮件或其他类型的通知。 而且,它们所代表的流程需要能够随着组织的变化而变化,而所管理的数据通常变化得要慢得多。

电脑游戏对手 AI是不言自明的。 不是我写的,但在与那些拥有这些系统的人的交谈中,这些系统通常是独立的。 换句话说,与工作流程不同,游戏通常没有能力改变用于控制计算机对手的过程。

Process Orchestration类似于工作流,但侧重于系统集成,而不是人员交互。 Apache Mule框架就是一个例子。 这里state可以描述状态(如started、in process、ended)和类型(eg ftp integration point, sql integration point)。

结论

与其他答案不同,我认为状态封装是管理软件系统变更的绝佳方式。 如果做得好,它会促进这些更改或使用户能够在运行时这样做。 您做出了更大灵活性的权衡,以换取更高的实施复杂性。 所以这种方法可能对购物车没有用,例如,行为可能是众所周知的并且不想改变。 另一方面,当过程可能发生变化时,它非常适合。

只是我的 2 美分,状态模式总是变得难以维护,因为没有编码的人很难理解。 我通常回退到旧的标准函数/方法指针数组,就像我以前的 C 经验一样。 您只需构建一个二维函数指针数组,其中包含行/列的状态/信号。 更容易理解。 你有一个管理它的类,你委托给其他类来处理复杂性......

my2c

大多数时候,状态模式设计中的状态处理多个状态(或状态的子状态),这使得它们更难维护。

如果一个状态在那里有任何类型的选择,它主要处理多个状态。

我需要很多纪律来保持状态清洁。

一个可能的解决方案是使更复杂的状态成为状态机本身 (HSM)。 这使得它在上层更具可读性,因为它必须处理更少的状态。

看看有限状态机 几乎每一种成熟的语言都有自己的好例子。 由于您没有指定首选语言,我将给您 C++ 示例: Boost FSM library 很可能它比您需要的要复杂得多,但它可以肯定地为您提供一些设计提示

所以我在寻找:

  • 实施状态模式时常见陷阱的示例以及如何避免它们,

状态模式不能很好地扩展。 想象一下具有 10 个状态和 10 种不同转换类型的状态机。 添加新状态意味着该状态必须定义所有 10 个转换。 添加新的转换意味着所有 10 个状态都必须定义它。 简而言之,如果您的状态机不稳定和/或您有很多状态/转换,请不要使用状态模式。

  • 正确完成状态模式的真实示例(例如在某些开源项目/框架中)

正确定义 :-) https://stackoverflow.com/a/2707195/1168342中引用的 Java 示例是针对 JSF Lifecycle 的,但我认为只有一个转换。 其他答案都没有为 State 引用任何内容。

  • 也欢迎使用状态模式的个人经验

Head First Design Patterns 使用 Gumball 机器示例来说明状态。 具有讽刺意味的是,每次他们扩展设计(添加新的状态或转换)时,都会有很多重复的代码(特别是对于特定状态内的无效转换)。 此外,根据谁决定下一个状态是什么,各个状态类可以相互耦合(状态间依赖)。 请参阅此答案末尾的解释: https ://stackoverflow.com/a/30424503/1168342。

GoF 书中提到基于表的替代方案具有优势,即它们的规律性。 更改转换标准需要更改表格(而不是代码)。

如果您对每个状态有不同的行为,则应该使用状态模式。 也许您需要在运行时重新配置转换。 使用它的另一个原因是您以后可能需要添加更多状态。

想象一下像 Chinese Checkers 这样的棋盘游戏,您有不同的 GUI 状态来选择 Pawn、选择目标插槽等。 在每个状态下,GUI 的行为应该不同,一些输入应该被处理,而另一些则被忽略。 使用简单的 switch/case 是可能的,但是状态模式会派上用场,因为逻辑被封装了,相关代码在一起了。 这使得在不影响大多数/所有其他状态的情况下更容易引入新状态(取决于谁负责设置转换:状态知道其传出转换,或者它们可以在运行时给出,例如使用构造函数)。

如您在此示例中所见,GuiController 使用 IGuiState 接口按需更改其行为。 一个实现可以在这里看到

主要的陷阱是在需要灵活时使用 switch/case。 由于间接寻址需要更多时间,我建议对于固定数量的相当简单的状态。 我必须实现一个相当快的低级网络协议,这通常会带来很大的开销。

我正在构建一个能够评估元素集的表达式评估器。 我发现状态模式对于根据集合的状态区分可以做什么和不能做什么非常有用。 即:打开、关闭、不活动、活动等。 FSM 非常容易绘制,并通过消除对大量 ifelse 语句块的需求来降低代码的复杂性来定义该功能根据其包含的属性应该做什么。 它通过将条件放入类中来使这些条件更加明确。 到目前为止,这是我最喜欢的模式之一。

在我的编程经验中,我遇到了一些状态模式的实现,并且做了一些。 我已经看到它们用于各种场景(主要是UI和解析)。 问题在于所有这些都在快速开发的压力下变成了难以维护和理解的代码块。 我正在考虑重构其中之一,但是在网上很难找到合适的资源。 在线上有许多简单的状态模式示例,但是我需要更多深入的资源。

所以我在寻找:

  • 实施状态模式时常见陷阱的示例以及如何避免它们,
  • 正确完成状态模式的真实示例(例如在某些开源项目/框架中)
  • 也欢迎有国家模式的个人经历

感谢您的时间

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM