在我的编程经验中,我遇到了一些状态模式的实现,并且做了一些。我已经看到它们用于各种场景(主要是 UI 和解析)。问题在于,所有这些在快速开发的压力下都变成了难以维护和理解的代码块。我正在考虑重构其中之一,但我在网上找不到好的资源。网上有很多简单的状态模式示例,但我需要一些更深入的资源。
所以我正在寻找:
- 实现状态模式时的常见陷阱示例以及如何避免它们,
- 正确完成状态模式的真实示例(例如在某些开源项目/框架中)
- 也欢迎使用状态模式的个人经验
感谢您的时间
在我的编程经验中,我遇到了一些状态模式的实现,并且做了一些。我已经看到它们用于各种场景(主要是 UI 和解析)。问题在于,所有这些在快速开发的压力下都变成了难以维护和理解的代码块。我正在考虑重构其中之一,但我在网上找不到好的资源。网上有很多简单的状态模式示例,但我需要一些更深入的资源。
所以我正在寻找:
感谢您的时间
@Ivan:网络上有许多可用于分层状态机(HSM) 的资源。Miro Samek 撰写了大量有关此设计模式的文章,并提供了许多有用的信息。
一些应该感兴趣的文章:
与 Mealy 和 Moore 描述的平面 FSM 状态图相比,使用 HSM 的最大好处是层次结构创建了责任分离。子状态只需要处理它们明确设计要处理的那些条件——未处理的事件被传递给父状态,如果父状态没有被明确设计为处理它,那么它被传递到下一个更高的状态父母等等。它允许您创建小型、可管理的状态机,每个状态机都服务于一个目的——一个可以容纳在单个对象中的状态机。随着新功能的添加或新类的添加,他们只需要处理自己的一小部分世界,并将未处理的事件传递给各自的父母。
如果正确实施,您将获得一个具有低圈复杂度的健壮程序,并且可以根据需要轻松修改或升级。
您可能已经读过,当状态改变包含该状态的某个对象的行为时,状态设计模式很有用。这意味着State
抽象类、接口或枚举类型的概念——尽管取决于Duck Typing的语言——它定义了任何常见的行为和/或所需的方法。
在使用状态模式时确实需要考虑两个关键方面:枚举和转换。枚举仅仅意味着识别一组可能的状态(例如一周中的几天),或者更抽象地识别状态的类型(即元状态),例如工作流引擎的开始、结束和中间状态。转换意味着决定如何对运动进行建模在状态之间,这通常是通过在表格表示(即有限状态机)中捕获所有可能的转换来完成的,或者让每个状态知道它可能“转换”到其他状态。
通常转换与元状态密切相关,因为在这样一个动态系统中,不可能提前知道所有状态和关系,在这种动态系统中,可以在运行时添加新状态以及转换。此外,通过转换方法,某些行为(例如通知)成为转换的一部分,而不是状态本身。
我已经处理或讨论了几种使用设施的场景:
我所说的工作流是指jBPM 之类的东西。像这样的系统关注的是在正确的时间获得正确的注意力、正确的人。他们通常会发送大量电子邮件或其他类型的通知。而且,它们所代表的流程需要能够随着组织的变化而变化,而所管理的数据通常变化得更慢。
电脑游戏对手 AI是不言自明的。不是我写的东西,但在与那些已经写过的人交谈时,这些系统通常是自给自足的。换句话说,与工作流程不同的是,游戏通常无法更改用于控制计算机对手的流程。
Process Orchestration类似于工作流,但侧重于系统集成,而不是人员交互。Apache Mule框架就是一个例子。这里的状态可以描述状态(例如开始、处理中、结束)和类型(例如ftp集成点、sql集成点)。
与其他答案不同,我认为状态封装是管理软件系统变更的绝佳方式。做得好,它可以促进这些更改或使用户能够在运行时这样做。您需要权衡更大的灵活性以换取更高的实现复杂性。因此,这种方法可能对购物车没有用处,例如,购物车的行为可能是众所周知的并且不喜欢改变。另一方面,当过程可能发生变化时,它非常适合。
只是我的 2 美分,状态模式总是变得难以维护,因为没有编码的人很难理解。我通常会回退到旧的标准函数/方法指针数组,就像我在旧的 C 经验中一样。您只需构建一个二维函数指针数组,其中包含行/列的状态/信号。更容易理解。你有一个类来管理它,你委托给其他类来处理复杂性......
my2c
大多数时候,状态模式设计中的状态处理的状态不止一种状态(或状态的子状态),这使得它们更难维护。
如果一个状态在那里有任何类型的选择,它主要处理多个状态。
我需要很多纪律来保持各州的清洁。
一个可能的解决方案是自己制作更复杂的状态机 (HSM)。这使得它在上层更具可读性,因为它必须处理更少的状态。
看看有限状态机。几乎每一种成熟的语言都有自己的好例子。由于您尚未指定首选语言,因此我将给您提供 C++ 示例:Boost FSM 库。很可能它比您需要的要复杂得多,但它可以肯定地为您提供一些设计提示
所以我正在寻找:
- 实现状态模式时的常见陷阱示例以及如何避免它们,
状态模式不能很好地扩展。想象一下一个有 10 种状态和 10 种不同转换类型的状态机。添加新状态意味着该状态必须定义所有 10 个转换。添加一个新的转换意味着所有 10 个状态都必须定义它。简而言之,如果您的状态机不稳定和/或您有很多状态/转换,请不要使用状态模式。
- 正确完成状态模式的真实示例(例如在某些开源项目/框架中)
正确定义:-) https://stackoverflow.com/a/2707195/1168342中引用的 Java 示例适用于 JSF 生命周期,但我认为只有一个转换。其他答案都没有为 State 引用任何内容。
- 也欢迎使用状态模式的个人经验
Head First Design Patterns 使用 Gumball 机器示例来说明状态。具有讽刺意味的是,每次他们扩展设计(添加新状态或转换)时,都会出现大量重复代码(尤其是对于特定状态内的无效转换)。此外,根据谁决定下一个状态是什么,各个状态类可以相互耦合(状态间依赖关系)。请参阅此答案末尾的说明:https ://stackoverflow.com/a/30424503/1168342 。
GoF 书中提到基于表格的替代方案具有优势,即它们的规律性。更改转换条件需要更改表(而不是代码)。
如果每个状态都有不同的行为,则应该使用状态模式。也许您需要在运行时重新配置转换。使用它的另一个原因是您以后可能必须添加更多状态。
想象一个像中国跳棋这样的棋盘游戏,你有不同的 GUI 状态来选择 Pawn、选择目标位置等等。在每个状态下,GUI 的行为应该不同,一些输入应该被处理,而另一些则忽略。使用简单的 switch/case 是可能的,但状态模式很方便,因为逻辑被封装,相关代码是在一起的。这使得在不影响大多数/所有其他状态的情况下更容易引入新状态(取决于谁负责设置转换:状态知道其传出转换,或者它们可以在运行时给出,例如使用构造函数)。
正如您在此示例中所见,GuiController 使用 IGuiState 接口来按需更改其行为。可以在这里看到一个实现。
当您需要灵活时,主要的缺陷是使用 switch/case。由于间接需要更多时间,我建议对于固定数量的相当简单的状态。我必须实现一个相当快的低级网络协议,这通常会带来很多开销。
我正在构建一个表达式评估器,它能够评估元素集。我发现状态模式对于根据状态来区分集合可以做什么和不可以做什么非常有用。即:打开,关闭,不活动,活动等。FSM 非常容易绘制,并且通过消除对大量 ifelse 语句的需要来根据其包含的属性来定义功能应该做什么,从而降低了代码的复杂性。它通过将条件放入类中来使这些条件更加明确。到目前为止,这是我最喜欢的图案之一。