“状态”是所有开发人员从初级编程进入中级编程时都会经历的一个常见编程术语。那么,“国家”一词究竟是什么意思呢?
通常,对象的状态只是对象的当前快照或其一部分。同时,在计算机科学中,程序的状态被定义为它关于先前存储的输入的位置。在这种情况下,“状态”一词的使用方式与科学中的使用方式相同:物体的状态,如气体、液体或固体,代表其当前的物理性质,而计算机程序的状态反映其当前的价值或内容。
存储的输入作为变量或常量保存在计算机程序中。在评估程序状态时,开发人员可能会检查这些输入中包含的值。程序运行时的状态可能会改变——变量可能会改变,内存值可能会改变。例如,控制变量(例如循环中使用的控制变量)会在每次迭代中改变程序的状态。检查程序的当前状态可用于测试或分析代码库。
在更简单的系统中,状态管理经常使用 if-else、if-then-else、try-catch 语句或布尔标志来处理;但是,当程序中有太多可以想象的状态时,这是无用的。它们可能导致难以理解、维护和调试的笨重、复杂的代码。
if-else-clauses 或 booleans 的一个缺点是它们可能会变得相当广泛,并且添加另一种状态很困难,因为它需要重写许多不同类的代码。假设您要制作一个具有主菜单、游戏循环和完成屏幕的游戏。
让我们构建一个视频播放器,例如:
class Video: def __init__(self, source): self.source = source self.is_playing = False self.is_paused = False self.is_stopped = True # A video can only be played when paused or stopped def play(self): if not self.is_playing or self.is_paused: # Make the call to play the video self.is_playing = True self.is_paused = False else: raise Exception( 'Cannot play a video that is already playing.' ) # A video can only be paused when it is playing def pause(self): if self.is_playing: # Make the call to pause the video self.is_playing = False self.is_paused = True else: raise Exception( 'Cannot pause a video that is not playing' ) # A video can only be stopped when it is playing or paused def stop(self): if self.is_playing or self.is_paused: # Make the call to stop the video self.is_playing = False self.is_paused = False else: raise Exception( 'Cannot stop a video that is not playing or paused' )
上面的代码片段是一个简单的视频播放器应用程序的 if-else 实现,其中三个基本状态是 - 播放、暂停和停止。但是,如果我们尝试添加更多状态,代码将迅速变得复杂、臃肿、重复,并且难以理解和测试。让我们看看添加另一个状态“rewind”时的代码是什么样的:
class Video: def __init__(self, source): self.source = source self.is_playing = False self.is_paused = False self.is_rewinding = False self.is_stopped = True # A video can only be played when it is paused or stopped or rewinding def play(self): if self.is_paused or self.is_stopped or self.is_rewinding: # Make the call to play the video self.is_playing = True self.is_paused = False self.is_stopped = False self.is_rewinding = False else: raise Exception( 'Cannot play a video that is already playing.' ) # A video can only be paused when it is playing or rewinding def pause(self): if self.is_playing or self.is_rewinding: # Make the call to pause the video self.is_playing = False self.is_paused = True self.is_rewinding = False self.is_stopped = False else: raise Exception( 'Cannot pause a video that is not playing or rewinding' ) # A video can only be stopped when it is playing or paused or rewinding def stop(self): if self.is_playing or self.is_paused or self.is_rewinding: # Make the call to stop the video self.is_playing = False self.is_paused = False self.is_stopped = True self.is_rewinding = False else: raise Exception( 'Cannot stop a video that is not playing or paused or rewinding' ) # 4. A video can only be rewinded when it is playing or paused. def rewind(self): if self.is_playing or self.is_paused: # Make the call to rewind the video self.is_playing = False self.is_paused = False self.is_stopped = False self.is_rewinding = True else: raise Exception( 'Cannot rewind a video that is not playing or paused' )
如果没有状态模式,您将不得不在整个代码中检查程序的当前状态,包括更新和绘制方法。如果要添加第四种状态,例如设置屏幕,则必须更新许多不同类的代码,这很不方便。这就是状态机的想法派上用场的地方。
状态机在计算机科学中并不是一个新概念。它们是软件业务中使用的基本设计模式之一。它比面向编码更面向系统,用于围绕用例建模。
让我们看一个通过 Uber 租用出租车的简单现实示例:
屏幕 1 是此用例中所有用户看到的第一个屏幕,它是独立的。屏幕 2 依赖于屏幕 1,在您在屏幕 1 上提供准确数据之前,您将无法进入屏幕 2。同样,屏幕 3 依赖于屏幕 2,而屏幕 4 依赖于屏幕 3。如果您都没有您的司机也不会取消您的行程,您将被移至屏幕 4,您将无法在当前行程结束之前计划另一次行程。
假设下大雨,没有司机接受您的行程,或者在您所在地区找不到可用的司机来完成您的行程;将显示一条错误通知,警告您驱动程序不可用,并且您仍停留在屏幕 3 上。您仍可以返回屏幕 2、屏幕 1,甚至是第一个屏幕。
您处于出租车预订流程的不同步骤,只有在当前阶段的指定操作成功后,您才能进入下一个级别。例如,如果您在屏幕 1 上输入了错误的位置,您将无法进入屏幕 2,并且您将无法进入屏幕 3,除非您在屏幕 2 上选择旅行选项,但您可以除非您的行程已经预订,否则请始终返回上一阶段。
在上面的示例中,我们将出租车预订流程划分为多个活动,每个活动可能会或可能不会根据预订状态调用另一个活动。使用状态机对此进行建模。原则上,这些阶段/状态中的每一个都应该是自主的,只有在当前阶段(无论是否成功)完成后,才会召唤下一个阶段/状态。
用更专业的术语来说,状态机使我们能够将一个大而复杂的动作分解为一系列独立的较小活动,例如前面示例中的出租车预订活动。
事件连接较小的任务,从一种状态转移到另一种状态称为转换。我们通常会在从一种状态切换到另一种状态后执行一些操作,例如在后端创建预订、开具发票、保存用户分析数据、将预订数据捕获到数据库中、行程结束后触发付款等等.
因此,状态机的一般公式可以如下给出:
当前状态 + 一些动作 / 事件 = 另一个状态
让我们看看为一个简单的视频播放器应用程序设计的状态机是什么样子的:
我们可以使用转换在代码中实现它,如下所示:
from transitions import Machine class Video: # Define the states PLAYING = 'playing' PAUSED = 'paused' STOPPED = 'stopped' def __init__(self, source): self.source = source # Define the transitions transitions = [ # 1. A video can only be played when it is paused or stopped. {'trigger': 'play', 'source': self.PAUSED, 'dest': self.PLAYING}, {'trigger': 'play', 'source': self.STOPPED, 'dest': self.PLAYING}, # 2. A video can only be paused when it is playing. {'trigger': 'pause', 'source': self.PLAYING, 'dest': self.PAUSED}, # 3. A video can only be stopped when it is playing or paused. {'trigger': 'stop', 'source': self.PLAYING, 'dest': self.STOPPED}, {'trigger': 'stop', 'source': self.PAUSED, 'dest': self.STOPPED}, ] # Create the state machine self.machine = Machine{ model = self, transitions = transitions, initial = self.STOPPED } def play(self): pass def pause(self): pass def stop(self): pass
现在,如果我们想添加另一个状态,比如倒带,我们可以很容易地做到这一点,如下所示:
from transitions import Machine class Video: # Define the states PLAYING = 'playing' PAUSED = 'paused' STOPPED = 'stopped' REWINDING = 'rewinding' # new def __init__(self, source): self.source = source # Define the transitions transitions = [ # 1. A video can only be played when it is paused or stopped. {'trigger': 'play', 'source': self.PAUSED, 'dest': self.PLAYING}, {'trigger': 'play', 'source': self.STOPPED, 'dest': self.PLAYING}, {'trigger': 'play', 'source': self.REWINDING, 'dest': self.PLAYING}, # new # 2. A video can only be paused when it is playing. {'trigger': 'pause', 'source': self.PLAYING, 'dest': self.PAUSED}, {'trigger': 'pause', 'source': self.REWINDING, 'dest': self.PAUSED}, # new # 3. A video can only be stopped when it is playing or paused. {'trigger': 'stop', 'source': self.PLAYING, 'dest': self.STOPPED}, {'trigger': 'stop', 'source': self.PAUSED, 'dest': self.STOPPED}, {'trigger': 'stop', 'source': self.REWINDING, 'dest': self.STOPPED}, # new # 4. A video can only be rewinded when it is playing or paused. {'trigger': 'rewind', 'source': self.PLAYING, 'dest': self.REWINDING}, #new {'trigger': 'rewind', 'source': self.PAUSED, 'dest': self.REWINDING}, # new ] # Create the state machine self.machine = Machine{ model = self, transitions = transitions, initial = self.STOPPED } def play(self): pass def pause(self): pass def stop(self): pass def rewind(self): pass
因此,我们可以看到状态机如何简化复杂的实现并避免我们编写不正确的代码。了解了状态机的功能后,现在重要的是要了解为什么以及何时使用状态机。
状态机可用于具有不同状态的应用程序。每个阶段都可以导致一个或多个后续状态,以及结束流程流。状态机使用用户输入或状态内计算来选择接下来要进入的状态。
许多应用程序需要一个“初始化”阶段,然后是允许各种操作的默认状态。先前和当前的输入以及状态都会对执行的操作产生影响。然后可以在系统“关闭”时执行清理措施。
如果我们可以将一个非常复杂的工作分解成更小的独立单元,状态机可以帮助我们更抽象地概念化和管理这些单元,我们只需要描述一个状态何时可以转换到另一个状态以及转换发生时会发生什么。我们不需要关心设置后转换是如何发生的。在那之后,我们只需要考虑何时和什么,而不是如何。
此外,状态机让我们以非常可预测的方式查看整个状态过程;一旦设置了转换,我们就不必担心管理不善或错误的状态转换;只有正确配置了状态机,才可能发生不正确的转换。我们全面了解状态机中的所有状态和转换。
如果我们不使用状态机,我们要么无法在各种可能的状态下可视化我们的系统,要么我们有意或无意地将我们的组件紧密耦合在一起,或者我们正在编写许多 if-else 条件来模拟状态转换,这使单元测试和集成测试变得复杂,因为我们必须确保编写所有测试用例来验证所有条件和使用的分支的可能性。
状态机除了能够开发决策算法外,还是应用规划的功能形式。随着应用程序变得越来越复杂,对有效设计的需求也在增长。
状态图和流程图在整个设计过程中都很有用,有时甚至是必不可少的。状态机不仅对应用程序规划很重要,而且易于创建。
以下是现代计算中状态机的一些主要优势:
并非关于状态机的一切都是好的,它们有时也会导致缺点和挑战。以下是状态机的一些常见问题:
使用状态机时,理想情况下您的系统应该有两个逻辑组件:
状态机可以被认为是驱动状态转换的基础设施。它验证状态转换并在转换之前、期间和之后执行配置的操作;但是,它不应该知道在这些操作中执行了什么业务逻辑。
因此,一般来说,使用正确的抽象将状态机与核心业务逻辑隔离是一个好主意;否则,管理代码将是一场噩梦。
以下是我们需要谨慎使用状态机逻辑的其他一些现实场景:
以下是我们日常生活中受益于状态机概念的一些实际应用:
例如,当您从在线电子商务网站购买商品时,它会经历多个阶段,例如订购、包装、发货、取消、已交付、已付款、退款等。当物品通过仓库或物流中心并在各个阶段进行扫描时,这种转换会自动发生,例如当用户取消或想要退款时。
状态机的概念在编程中非常有用。它不仅简化了开发更复杂用例应用程序的过程,还减少了必要的开发工作。它提供了对现代事件的更简单和优雅的掌握,如果正确应用,可能会创造奇迹。