如何正确使用状态模式?

我在编程经验中遇到了一些状态模式的实现,并做了一些。 我已经看到他们在各种情况下使用(主要是用户界面和parsing)。 麻烦的是,它们都在快速发展的压力下变成了几乎不可维护和可以理解的代码块。 我正在考虑重构其中的一个,但我很难find这个在线的好资源。 在线状态模式有很多简单的例子,但是我需要更深入的资源。

所以我在找:

  • 当执行状态模式和如何避免它们时常见的陷阱的例子,
  • 现实世界中的状态模式正确完成的例子(如在一些开源项目/框架中)
  • 个人经验与国家格局也受到欢迎

感谢您的时间

@Ivan:Web上有许多可用于分层状态机 (HSM)的资源。 Miro Samek已经撰写了大量关于这种devise模式的文章,并提供了大量有用的信息。

一些应该感兴趣的文章:

  • 面向状态的编程(SOP)(Samek) (pdf)
  • 分层状态机 – 基本重要的devise方法(Samek) (pdf)
  • C / C ++(Samek)实用状态图表 (link to google-books)
  • 面向状态的编程(by Asher Sterkin) (pdf)

Mealy和Moore描述的使用HSM平坦FSM状态图的最大好处在于层次结构创build了责任分离。 子状态只需要处理它们明确devise要处理的条件 – 未处理的事件会传递给父状态,如果父状态没有明确的devise来处理它,那么它将被传递到下一个更高的状态家长等等。 它允许您创build小型,可pipe理的状态机,每个状态机用于一个单一的目的 – 一个可以在一个单一的对象。 随着新function的增加,或新增class级的增加,他们只需要处理自己的小部分世界,并将未处理的事件传递给各自的父母。

当正确执行时,您将得到一个具有较低圈复杂度的强大程序,可以根据需要轻松修改或升级。

正如你可能已经读过的那样, 状态devise模式在状态改变某个包含该状态的对象的行为时非常有用。 这意味着一个抽象类,接口或枚举types的概念 ,尽pipe取决于Duck Typing的语言,它将定义任何常见的行为和/或所需的方法。

关键方面

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

通常情况下,转换与元状态并行,因为在dynamic系统中,不可能提前知道所有的状态和关系,在这种系统中,可以在运行时添加新的状态,从而转换。 另外,通过转换方法,某些行为(例如通知)成为转换的一部分,而不是状态本身。

例子

有几个场景我已经或正在讨论这是一个使用设施:

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

通过工作stream程我的意思是像jBPM 。 像这样的系统关心的是在正确的时间对合适的人进行正确的关注。 他们通常会发送大量电子邮件或其他types的通知。 而且,他们所代表的stream程需要随着组织变化而改变的能力,而被pipe理的数据通常变化更慢。

电脑游戏对手AI是自我解释。 不是我写的东西,而是和那些有话语权的人交谈,这些系统通常是自成体系的。 换句话说,与工作stream程不同,游戏通常不具备改变用于控制计算机对手的过程的能力。

stream程编排与工作stream程类似,但侧重于系统集成,而不是人员交互。 Apache Mule框架就是一个例子。 这里的状态可以描述状态(例如启动,进程中,结束)和types(例如ftp集成点,sql集成点)。

结论

与其他答案不同,我认为状态封装是pipe理软件系统变化的一个好方法。 做得好,它促进了这些变化,或使用户在运行时这样做。 为了增加实现的复杂性,您需要权衡更多的灵活性。 所以这样的方法可能对购物车没有用处,例如行为可能是非常知名的并且不想改变。 另一方面,当这个过程可能发生变化时,它会非常合适。

只是我的2美分,国家模式总是变得难以维持,因为那些没有编码的人很难理解。 我通常回退到旧的标准数组函数/方法指针,就像我以前的C经验。 你只是build立一个两维数组的函数指针与行/列的状态/信号。 更容易理解。 你有一个类来pipe理,你委托给其他类来处理复杂性…

MY2C

大多数时候,处于国家模式devise的国家正在处理更多的一个国家(或国家的子状态),这使得它们难以维护。

如果一个国家在那里有任何一种select,那么它主要处理一个以上的国家。

我需要很多纪律来保持国家的清洁。

一个可能的解决办法是让状态机本身更复杂(HSM)。 这使得上层的可读性更高,因为它必须处理更less的状态。

看看有限状态机 。 几乎每一种成熟的语言都有自己的好例子。 既然你没有指定你的首选语言,我会给你C ++的例子: Boost FSM库 。 很可能它比你需要的复杂得多,但它肯定会给你一些devise提示

所以我在找:

  • 当执行状态模式和如何避免它们时常见的陷阱的例子,

国家格局不好。 试想一下,具有10个状态和10个不同转换types的状态机。 添加新状态意味着状态必须定义全部10个转换。 添加一个新的转换意味着所有的10个州必须定义它。 简而言之,如果你的状态机不稳定和/或你有很多状态/转换,不要使用状态模式。

  • 现实世界中的状态模式正确完成的例子(如在一些开源项目/框架中)

定义正确 🙂 https://stackoverflow.com/a/2707195/1168342中引用的Java示例是针对JSF生命周期的,但我认为只有一个转换。; 没有其他的答案引用任何国家。

  • 个人经验与国家格局也受到欢迎

Head First Design Patterns使用Gumball机器示例来说明状态。 这很讽刺,但每次扩展devise(添加新的状态或转换)时,都会有很多重复的代码(特别是在特定状态下无效的转换)。 而且,根据谁决定下一个状态是什么,各个状态类可以彼此耦合(状态间依赖关系)。 请参阅此答案结尾的说明: https : //stackoverflow.com/a/30424503/1168342 。

GoF书提到基于表格的替代scheme有其优点,即它们的规律性。 更改转换条件需要更改表(而不是代码)。

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

想象一下,像中国跳棋这样的棋盘游戏,你有不同的GUI状态来select一个Pawn,select一个目标插槽等等。 在每种状态下,GUI应该有不同的performance,有些input应该被别人忽略。 使用一个简单的开关/情况是可能的,但是状态模式随着逻辑被封装而变得方便,相关的代码也是可以的。 这使得在不影响大多数/所有其他状态的情况下(取决于负责设置转换的状态:状态知道其输出转换,或者可以在运行时例如使用构造器)可以更容易地引入新的状态。

正如你在这个例子中看到的,GuiController使用一个IGuiState接口来按需改变它的行为。 一个实现可以在这里看到 。

当你需要灵活时,主要的缺陷是使用开关/shell。 由于间接需要更多的时间,我build议对于固定数量的相当简单的书架。 我必须实现一个相当快速的低级别networking协议,这通常是很大的开销。

我正在构build一个能够评估元素集的expression式评估器。 我发现状态模式对于根据状态区分什么可以和不可以做到什么是非常有用的。 即:开放,封闭,无效,有效等。 FSM非常容易绘制,并且通过消除大量ifelse语句块来定义该function应根据其所包含的属性来执行哪些操作,从而降低了代码的复杂性。 它通过将条件变为类来使这些条件更加明确。 这是迄今为止我最喜欢的模式之一。