圣诞大减价开始了!

状态

意图

状态是一种行为设计模式,它允许对象在其内部状态改变时改变其行为。它看起来好像对象改变了它的类。

状态设计模式

问题

状态模式与的概念密切相关有限状态机

有限状态机

有限状态机。

主要思想是,在任何给定的时刻,都有一个有限的的数量程序可以在其中。在任何唯一状态下,程序的行为都是不同的,程序可以从一种状态瞬间切换到另一种状态。但是,根据当前状态,程序可能会切换到某些其他状态,也可能不会。这些转换规则叫做转换,也是有限和预定的。

您还可以将此方法应用于对象。假设我们有一个文档类。文档可以处于以下三种状态之一:草案适度而且发表.的发布文档的方法在每种状态下的工作方式略有不同:

  • 草案,它将文档移动到适度。
  • 适度,则仅当当前用户是管理员时,才会公开文档。
  • 发表,它什么都不做。
文档对象的可能状态

文档对象的可能状态和转换。

状态机通常由许多条件语句实现(如果开关),根据对象的当前状态选择适当的行为。通常,这个“状态”只是对象字段的一组值。即使您以前从未听说过有限状态机,您也可能至少实现过一次状态。下面的代码结构听起来耳熟吗?

类文档是字段状态:string //…方法publish()是switch (state)"draft": state = " moderate " break " moderate ": if (currentUser. exe)role == "admin") state = "published" break "published": //不做任何操作。Break //…

对象中添加越来越多的状态和依赖状态的行为时,基于条件的状态机的最大弱点就会暴露出来文档类。大多数方法将包含怪异的条件,根据当前状态选择方法的正确行为。这样的代码很难维护,因为对转换逻辑的任何更改都可能需要更改每个方法中的状态条件。

随着项目的发展,问题会变得越来越大。在设计阶段预测所有可能的状态和过渡是相当困难的。因此,使用有限的条件集构建的精简状态机可能会随着时间的推移而变得臃肿不堪。

解决方案

State模式建议为对象的所有可能状态创建新类,并将所有特定于状态的行为提取到这些类中。

而不是实现它自己的所有行为,原始对象,称为上下文,存储对一个表示其当前状态的状态对象的引用,并将所有与状态相关的工作委托给该对象。

文档将工作委托给一个状态对象

文档将工作委托给一个状态对象。

若要将上下文转换为另一种状态,请将活动状态对象替换为表示该新状态的另一个对象。只有当所有状态类遵循相同的接口并且上下文本身通过该接口与这些对象一起工作时,这才有可能。

这个结构看起来类似于策略模式,但有一个关键的区别。在状态模式中,特定的状态可能彼此知道,并发起从一个状态到另一个状态的转换,而策略之间几乎不知道彼此。

真实的模拟

智能手机中的按钮和开关根据设备的当前状态表现不同:

  • 当手机解锁后,按下按钮就可以执行各种功能。
  • 当手机处于锁定状态时,按任意键进入解锁界面。
  • 当手机电量不足时,按下任何按钮就会显示充电画面。

结构

状态设计模式的结构 状态设计模式的结构
  1. 上下文存储对一个具体状态对象的引用,并将所有特定于状态的工作委托给该对象。上下文通过状态接口与状态对象通信。上下文公开了一个setter,用于向它传递一个新的状态对象。

  2. 状态接口声明特定于状态的方法。这些方法应该对所有具体状态都有意义,因为您不希望某些状态具有永远不会被调用的无用方法。

  3. 具体的国家为特定于状态的方法提供它们自己的实现。为了避免在多个状态之间重复类似的代码,您可以提供封装一些公共行为的中间抽象类。

    状态对象可以存储对上下文对象的反向引用。通过这个引用,状态可以从上下文对象中获取所需的任何信息,以及初始化状态转换。

  4. 上下文和具体状态都可以设置上下文的下一个状态,并通过替换链接到上下文的状态对象来执行实际的状态转换。

伪代码

在本例中,状态模式允许媒体播放器的相同控件根据当前播放状态表现不同。

状态模式示例的结构

使用状态对象改变对象行为的示例。

播放器的主对象总是链接到为播放器执行大部分工作的状态对象。一些动作将玩家的当前状态对象替换为另一个,这改变了玩家对用户交互的反应方式。

// AudioPlayer类充当上下文。它还维护了一个状态类实例的//引用,该状态类表示音频播放器的当前状态。状态字段UI,音量,播放列表,currentSong构造函数AudioPlayer()是这个。state = new ReadyState(this) // Context将用户输入委托给一个state //对象。当然,结果取决于当前激活的状态//,因为每个状态可以以不同的方式处理//输入。UI = new UserInterface() UI. lockbutton . onclick (this.clickLock) UI. playbutton . onclick (this.clickPlay) UI. nextbutton . onclick (this.clickNext) UI. prevbutton . onclick (this.clickPrevious) //其他对象必须能够切换音频播放器的//激活状态。method changeState(state: state)是这样的。state = state // UI方法将执行委托给活动状态。方法clickLock()是状态。方法clickPlay()是状态。方法clickPlay()是状态。方法clickNext()是状态。方法clickNext()是状态。方法clickPrevious()是状态。方法startPlayback()是//…方法stopPlayback()是//… method nextSong() is // ... method previousSong() is // ... method fastForward(time) is // ... method rewind(time) is // ... // The base state class declares methods that all concrete // states should implement and also provides a backreference to // the context object associated with the state. States can use // the backreference to transition the context to another state. abstract class State is protected field player: AudioPlayer // Context passes itself through the state constructor. This // may help a state fetch some useful context data if it's // needed. constructor State(player) is this.player = player abstract method clickLock() abstract method clickPlay() abstract method clickNext() abstract method clickPrevious() // Concrete states implement various behaviors associated with a // state of the context. class LockedState extends State is // When you unlock a locked player, it may assume one of two // states. method clickLock() is if (player.playing) player.changeState(new PlayingState(player)) else player.changeState(new ReadyState(player)) method clickPlay() is // Locked, so do nothing. method clickNext() is // Locked, so do nothing. method clickPrevious() is // Locked, so do nothing. // They can also trigger state transitions in the context. class ReadyState extends State is method clickLock() is player.changeState(new LockedState(player)) method clickPlay() is player.startPlayback() player.changeState(new PlayingState(player)) method clickNext() is player.nextSong() method clickPrevious() is player.previousSong() class PlayingState extends State is method clickLock() is player.changeState(new LockedState(player)) method clickPlay() is player.stopPlayback() player.changeState(new ReadyState(player)) method clickNext() is if (event.doubleclick) player.nextSong() else player.fastForward(5) method clickPrevious() is if (event.doubleclick) player.previous() else player.rewind(5)

适用性

当您有一个对象,其行为取决于其当前状态,状态数量非常多,特定于状态的代码频繁更改时,请使用State模式。

该模式建议将所有特定于状态的代码提取到一组不同的类中。因此,您可以独立地添加新状态或更改现有状态,从而降低维护成本。

当一个类被大量的条件所污染,这些条件会根据类字段的当前值改变类的行为时,请使用该模式。

State模式允许您将这些条件的分支提取到相应状态类的方法中。在这样做的同时,还可以从主类中清除特定于状态的代码中涉及的临时字段和辅助方法。

当您在相似的状态和基于条件的状态机的转换中有大量重复代码时,请使用State。

State模式允许您组合状态类的层次结构,并通过将公共代码提取到抽象基类中来减少重复。

如何实施

  1. 决定什么类将充当上下文。它可以是一个已经有状态依赖代码的现有类;或者是一个新类,如果特定于状态的代码分布在多个类中。

  2. 声明状态接口。尽管它可以镜像上下文中声明的所有方法,但只针对那些可能包含特定于状态的行为的方法。

  3. 对于每个实际状态,创建一个从状态接口派生的类。然后查看上下文的方法,并将与该状态相关的所有代码提取到新创建的类中。

    在将代码移到状态类时,您可能会发现它依赖于上下文的私有成员。有几种变通方法:

    • 将这些字段或方法设为公共的。
    • 将您正在提取的行为转换为上下文中的公共方法,并从状态类调用它。这种方法虽然不美观,但速度很快,而且以后你总是可以修复它。
    • 将状态类嵌套到上下文类中,但前提是您的编程语言支持嵌套类。
  4. 在上下文类中,添加一个状态接口类型的引用字段和一个允许重写该字段值的公共setter。

  5. 再次检查上下文的方法,将空的状态条件替换为对状态对象的相应方法的调用。

  6. 要切换上下文的状态,请创建一个状态类的实例并将其传递给上下文。您可以在上下文本身中,或在各种状态中,或在客户端中执行此操作。无论在哪里执行此操作,该类都会依赖于它实例化的具体状态类。

利与弊

  • 单一责任原则.将与特定状态相关的代码组织到单独的类中。
  • 打开/关闭原则.在不改变现有状态类或上下文的情况下引入新的状态。
  • 通过消除庞大的状态机条件来简化上下文代码。
  • 如果状态机只有少数几个状态或几乎没有变化,那么应用该模式可能会过度。

与其他模式的关系

  • 状态策略(在某种程度上适配器)的结构非常相似。实际上,所有这些模式都基于组合,即将工作委托给其他对象。然而,它们都解决不同的问题。模式不仅仅是以特定的方式构造代码的秘方。它还可以将模式解决的问题传达给其他开发人员。

  • 状态可以认为是策略.这两种模式都基于组合:它们通过将一些工作委托给helper对象来改变上下文的行为。策略使这些对象完全独立,彼此不知道。然而,状态不限制具体状态之间的依赖关系,允许它们随意更改上下文的状态。

代码示例

c#中的状态状态在c++中Go中的状态Java中的状态PHP中的状态Python中的状态Ruby中的状态生锈的状态Swift状态TypeScript中的状态

Baidu
map