【问题标题】:How to use state pattern correctly?如何正确使用状态模式?
【发布时间】:2011-06-23 13:50:18
【问题描述】:

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

所以我正在寻找:

  • 常见陷阱示例 实现状态模式以及如何 避开他们,
  • 现实世界的例子 状态模式正确完成(如 一些开源项目/框架)
  • 个人经历与状态 也欢迎使用模式

感谢您的宝贵时间

【问题讨论】:

  • 如果您有很多状态,例如在 3D 游戏中,则不适合。
  • 那么你建议什么样的替代方案?
  • 状态模式定义的链接怎么样?
  • @Dave O.:您可以实施分层状态机 (HSM) 将问题分解为可管理大小的超状态和子状态。

标签: oop design-patterns state-pattern


【解决方案1】:

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

一些应该感兴趣的文章:

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

如果实施得当,您将获得一个圈复杂度低的健壮程序,并且可以根据需要轻松修改或升级。

【讨论】:

    【解决方案2】:

    您可能已经阅读过,当状态改变包含该状态的某个对象的行为时,State Design Pattern 很有用。这意味着State 抽象类、接口或enumerated type 的概念——尽管Duck Typing 的语言也会这样做——它定义了任何常见的行为和/或所需的方法。

    关键方面

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

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

    示例

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

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

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

    电脑游戏对手 A.I. 不言自明。不是我写的,但在与那些已经写过的人交谈时,这些系统通常是自给自足的。换句话说,与工作流程不同的是,游戏通常无法更改用于控制计算机对手的流程。

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

    结论

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

    【讨论】:

      【解决方案3】:

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

      my2c

      【讨论】:

      • @Pangea:可惜不在我当前的代码库中。如果我有时间,我会尝试在我的答案中添加代码......
      【解决方案4】:

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

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

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

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

      【讨论】:

      • 这个标准:“如果一个状态有任何类型的选择,它主要处理多个状态。”对于确定是否应该将状态一分为二非常有帮助。
      【解决方案5】:

      看看Finite State Machine。几乎每一种成熟的语言都有自己的好例子。由于您尚未指定首选语言,因此我将给您提供 C++ 示例:Boost FSM library。很可能它比您需要的要复杂得多,但它确实可以为您提供一些设计提示

      【讨论】:

        【解决方案6】:

        所以我正在寻找:

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

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

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

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

        • 也欢迎个人体验状态模式

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

        GoF 书中提到table-based alternatives 有优势,即它们的规律性。更改转换条件需要更改表(而不是代码)。

        【讨论】:

        • 很好的答案!谢谢。
        【解决方案7】:

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

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

        正如您在this example 中看到的,GuiController 使用 IGuiState 接口按需更改其行为。一个实现can be seen here

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

        【讨论】:

          【解决方案8】:

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2020-02-26
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-04-06
            • 1970-01-01
            相关资源
            最近更新 更多