状态机和状态模式的实现有什么区别?

49

我想知道一个状态机是否只是状态模式的功能,还是这两者之间实际存在差异?

我找到了一篇题为“状态设计模式与状态机”的文章,但说到底,他仅表示状态模式使状态机过时,但并没有描述状态机状态模式的实现有什么具体区别。


他确切地说了什么,使得状态机变得过时了? - Henrique Barcelos
1
他写道:“此外,我曾看到有人使用状态机代替状态设计模式,但这会导致软件变得非常复杂且难以维护。在使用面向对象编程语言时,没有理由再使用状态机了。” - Christoph
我认为他指的是全局状态机,比如OpenGL。但他错了……例如,在计算机图形学中,你必须尽可能地提高性能,即使这意味着编写一个全局状态机。并没有一套固定做法…… - Henrique Barcelos
7个回答

28
我向同事描述这种差异的方式是,状态模式是许多独立封装状态的更为分散的实现,而状态机则更加整体化。状态机的整体化意味着单个状态在不同机器中重用将更加困难,并且将状态机拆分成多个编译单元也更加困难。另一方面,这种整体化设计允许更好地优化状态机,并允许许多实现在一个表中表示所有转换信息。这特别适用于负责状态机架构或功能的人对其实现的编程语言不熟悉的情况。请记住,许多工程和数学专业的人已经学习了状态机,但在编程领域没有或几乎没有教育。向这些类型的人展示转换、操作和保护的表格要比展示页面上的状态模式容易得多。
虽然这篇文章确实很有价值,但我在几个观点上不同意作者。

请注意,切换状态需要分配!这将导致速度变慢。这可以通过在一个缓冲区中将所有状态放置在一起来进行修复,以节省缓存丢失或两个。但是,这将需要对作者示例进行重大更改。

还要注意,未处理的事件无法像静态状态机中那样内联和优化掉,因为在状态模式中它们在动态间接层后面。这也可能会根据您的要求导致效率低下。

从维护的角度来看,需要注意的是使用状态模式无法从一个中央超级状态记录未处理的事件。此外,添加新的事件类型/处理程序函数需要向所有状态添加一个函数!我认为这不太容易维护。我更喜欢在表格中看到所有转换,而不是查看每个状态的内部工作。作者正确地指出添加状态更容易,但只有非常微小的差异,例如使用boost statecharts,我只需将状态添加到其父级子状态列表中,这才是真正的区别。我在速度不是问题且状态机层次结构很可能保持平坦的情况下使用状态模式。作者正确地指出,与状态机相比,状态模式的初始实现通常更容易,并且通常应该有更多的程序员使用状态机。状态模式的一个论点是它允许实现“开放封闭”状态机,其中可以在库中定义状态机,然后由用户进行扩展,这在我所知道的主流状态机框架中是不可能的。

18

如果还有人感兴趣,这是我的看法:

在状态机中,对象可以处于不同的状态,但我们并不真正关心这些状态下它们如何行为。事实上,我们只关心当对象转换到下一个状态时应用了什么操作。如果你在Java中实现一个状态机,则状态将仅是一个枚举或字符串,并且将有一个Transition类,其中包含doAction()方法。

另一方面,在状态模式中,您并不真正关心转换,而是关心对象在这些状态下如何行为。转换只是一种实现细节,可以使状态行为相互解耦。每个状态将是一个单独的类,具有自己的doAction()方法。

说状态模式使状态机过时是不正确的。如果每个状态的行为很重要,例如在游戏编程中,对象可以拥有“空闲”,“攻击”,“奔跑”等状态,并且在每个状态中,您希望实现对象的行为,则状态模式将非常有用。

但对于像在线订购产品这样的用例,您并不关心订单对象如何行为。您只关心订单是否处于“已添加到购物车”状态,当发布“付款完成”事件时,然后将其更改为“正在处理”状态。在这种情况下,状态是订单类的简单枚举属性,因此使用状态机更好。


关于你最后一段的问题,是的,我理解并且认为状态机是一个不错的选择。但从某种角度来看,它是否需要控制行为呢?例如,如果图表处于处理中的状态,我就不能添加订单。总结起来,我的意思是根据实际状态,总会存在一些条件,某些操作无法执行。 - Álvaro García

12

状态机可以用多种方式进行设计和实现。一种方法是使用《设计模式》中所描述的状态模式。但是还有其他模式可以实现状态机。

例如,您可以查看Miro Samek的研究,并阅读他的书籍《Practical UML statecharts in C/C++, 2nd ed. (Event-Driven Programming for Embedded Systems)》。

您还可能会发现这个问题很有趣。


2
我不同意。状态设计模式并不是用来实现状态机的。状态设计模式的重点在于封装行为以创建可重用、可维护的组件。有限状态机的重点是状态及其转换(由状态图表示),而不是实际行为(这是一项实现细节)。您可以在我写的这篇文章中找到我的完整论述:https://medium.com/@1gravityllc/the-super-state-design-pattern-166127ce7c9a - Emanuel Moecklin

3

我注意到状态模式有所不同。在使用UI时更方便。比如说,我想锁定状态,在状态模式的上下文中,我可以创建一个布尔值来防止状态进一步变化。

这里是一个 Kotlin 的例子:

     inner class StateContext : State {

       private var stateContext: State? = null
       private var lockState: Boolean = false

       fun isLockState(): Boolean {
           return lockState
       }

       fun setLockState(lockState: Boolean): StateContext {
           this.lockState = lockState//no further actions allowed. useful if you need to permenatley lock out the user from changing state.
           return this
       }

       fun getState(): State? {
           return this.stateContext
       }

       fun setState(state: State): StateContext {
           if (!lockState) this.stateContext = state
           return this
       }

       override fun doAction() {
           this.stateContext?.doAction()
       }
   }

对于状态机,我不确定如何轻松地完成它。如果只是关注状态(比如保存当前状态的枚举),而不是实际的实现细节(例如更改 UI 按钮颜色),我确实很喜欢状态机。状态机好的一点是你可以有一个中央位置来记录状态变化。我看到了 Tinder 开发的 Kotlin 库,链接在这里,它看起来非常有趣。但我个人认为,你可以将它们全部更改成你想要的方式,只是一种更干净的方式而已。

那么什么是状态机呢?状态机更关心“下一个状态” ,强调状态转换而不是状态的细节。它维护流程。通常使用枚举来创建状态机。这篇文章帮助清楚地阐述了这个概念(下面的参考文献也取自该文章),但本质上这就是一个状态机:

public enum LeaveRequestState {

Submitted {
    @Override
    public LeaveRequestState nextState() {
        return Escalated;
    }

    @Override
    public String responsiblePerson() {
        return "Employee";
    }
},
Escalated {
    @Override
    public LeaveRequestState nextState() {
        return Approved;
    }

    @Override
    public String responsiblePerson() {
        return "Team Leader";
    }
},
Approved {
    @Override
    public LeaveRequestState nextState() {
        return this;
    }

    @Override
    public String responsiblePerson() {
        return "Department Manager";
    }
};

public abstract LeaveRequestState nextState(); 
public abstract String responsiblePerson();

现在你知道每个事件的下一个转换状态是什么了。因此,状态机对于状态的实际实现并不是很关心,而是更关注于转换:

LeaveRequestState state = LeaveRequestState.Submitted;

state = state.nextState();
assertEquals(LeaveRequestState.Escalated, state);

state = state.nextState();
assertEquals(LeaveRequestState.Approved, state);

state = state.nextState();
assertEquals(LeaveRequestState.Approved, state);

使用状态机,我不确定如何轻松地完成这项工作。这正是状态机的用途,控制状态流程并强制执行仅有效的状态转换和状态。有许多实现状态机的方法,处理状态转换的方式可以大相径庭。使用枚举处理状态是一种相当粗糙的实现,不提供太多灵活性,因此使用该方法锁定状态可能确实存在问题。更灵活的方法是让状态处理触发器并分派到下一个状态。 - Emanuel Moecklin
使用枚举确实是正确的。 - j2emanue

2
我写了一篇关于状态设计模式和状态机的文章:https://medium.com/@1gravityllc/the-super-state-design-pattern-166127ce7c9a。在这篇文章中,我展示了它们的重点不同,但它们也不是互斥的。有一种方法可以将两者结合起来,形成一个超级状态设计模式。
状态设计模式的重点在于封装行为以创建可重用、可维护的组件(状态)。
有限状态机的重点在于状态及其转换(由状态图表达),而不在于实际的行为(这是一个实现细节)。
本文描述了如何通过使用有限状态机来描述和管理对象的状态及其转换,并使用状态设计模式将行为委托给状态对象来结合这两个概念。

2
TL;DR: 状态机就是状态模式在工作,但实际上这两者之间有区别。
完整回答:它们之间有很大的区别。状态模式抽象出状态并将其解耦。例如,您可以轻松地用另一个状态替换特定状态。然而,在添加新状态和/或新转换时,您不希望重写所有状态。状态机抽象出状态图本身并将其与转换有效负载解耦。要更改特定状态,您必须修复整个图表。但要添加状态或转换,只需修复图表即可。

1
这是我理解的方式:
状态机是一个数学/工程术语。它是以状态图的形式呈现的数学模型,描述了计算机(如自动售货机或洗衣机)的状态和状态转换。
状态模式是编程中的一种名称。设计模式是为了更好地规范代码设计结构,以便于封装、重用和扩展;状态模式用于解决对象根据其状态显示不同行为的问题。
正确的关系是,如果您需要开发一个“洗衣机”程序,产品经理应该绘制一个“状态图”来描述“状态机”,然后将其交给程序员实现。程序员可以用多种方式实现状态机,但最佳的实现方式是使用状态模式。

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