python设计模式之状态模式

面向对象编程着力于在对象交互时改变它们的状态。在很多问题中,有限状态机(通常名为状态机)是一个非常方便的状态转换建模(并在必要时以数学方式形式化)工具。首先,什么是状态机?状态机是一个抽象机器,有两个关键部分,状态和转换。状态是指系统的当前(激活)状况。例如,假设我们有一个收音机,其两个可能的状态是在调频波段( FM)或调幅波段( AM)上调节。另一个可能的状态是从一个FM/AM无线电台切换到另一个。转换是指从一个状态切换到另一个状态,因某个事件或条件的触发而开始。通常,在一次转换发生之前或之后会执行一个或一组动作。假设我们的收音机被调到107 FM无线电台,一次状态转换的例子是收听人按下按钮切换到107.5 FM。

状态机的一个不错的特性是可以用图来表现(称为状态图),其中每个状态都是一个节点,每个转换都是两个节点之间的边。下图展示了一个典型操作系统进程的状态图(不是针对特定的系统)。进程一开始由用户创建好,就进入“已创建/新建”状态。这个状态只能切换到“等待”状态,这个状态转换发生在调度器将进程加载进内存并添加到“等待/预备执行”的进程队列之时。一个“等待”进程有两个可能的状态转换:可被选择而执行(切换到“运行”状态),或被更高优先级的进程所替代(切换到“换出并等待”状态)。

进程的其他典型状态还包括“终止”(已完成或已终止)、“阻塞”(例如,等待一个I/O操作完成)等。需要注意,一个状态机在一个特定时间点只能有一个激活状态。例如,一个进程不可能同时处于“已创建”状态和“运行”状态。

状态机可用于解决多种不同的问题,包括非计算机的问题。非计算机的例子包括自动售货机、

电梯、交通灯、暗码锁、停车计时器、自动加油泵及自然语言文法描述。计算机方面的例子包括

游戏编程和计算机编程的其他领域、硬件设计、协议设计,以及编程语言解析。

1. 现实生活的例子

这里再一次提到零食自动售货机(在之前的第10章中见过),它也是日常生活中状态模式的一个例子。自动售货机有不同的状态,并根据我们放入的钱币数量作出不同反应。根据我们的选择和放入的钱币,机器会执行以下操作。

  • [ ] 拒绝我们的选择,因为请求的货物已售罄。
  • [ ] 拒绝我们的选择,因为放入的钱币不足。
  • [ ] 递送货物,且不找零,因为放入的钱币恰好足够。
  • [ ] 递送货物,并找零。

2. 软件的例子

使用状态模式本质上相当于实现一个状态机来解决特定领域的一个软件问题。 django-fsm程

序包是一个第三方程序包,用于Django框架中简化状态机的实现和使用。

3. 应用案例

状态模式适用于许多问题。所有可以使用状态机解决的问题都是不错的状态模式应用案例。我们已经见过的一个例子是操作系统/嵌入式系统的进程模型。

编程语言的编译器实现是另一个好例子。词法和句法分析可使用状态来构建抽象语法树。

事件驱动系统也是另一个例子。在一个事件驱动系统中,从一个状态转换到另一个状态会触发一个事件/消息。许多计算机游戏都使用这一技术。例如,怪兽会在主人公接近时从防御状态转换到攻击状态。

4. 实现

状态设计模式通常使用一个父State类和许多派生的ConcreteState类来实现,父类包含所有状态共同的功能,每个派生类则仅包含特定状态要求的功能。然而在我看来,这些是实现细节。状态模式关注的是实现一个状态机,状态机的核心部分是状态和状态之间的转换。每个部分具体如何实现并不重要。

为避免重复造轮子,可以利用已有的Python模块。它们不仅能帮助我们创建状态机,而且还是地道的Python方式。我发现state_machine这个模块非常有用在进一步学习之前,如果你的系统上尚未安装state_machine,请使用下面的命令进行安装。

pip3 install state_machine

state_machine相当简单,不需要特别的介绍。我们将通过示例代码覆盖该模块的大部分内容。

首先从Process类开始。每个创建好的进程都有自己的状态机。使用state_machine模块创建状态机的第一个步骤是使用@acts_as_state_machine修饰器。

@acts_as_state_machine
class Process:

下一步,定义状态机的状态。这是我们在状态图中看到的节点的映射。唯一的区别是应指定状态机的初始状态。这可通过设置inital=True来指定。

created = State(initial=True)
waiting = State()
runnig = State()
terminated = State()
blocked = State()
swapped_out_waiting = State()
swapped_out_blocked = State()

接着定义状态转换。在state_machine模块中,一个状态转换就是一个Event。我们使用参数from_states和to_state来定义一个可能的转换。 from_states可以是单个状态或一组状态(元组)。

wait = Event(from_states=(created, running, blocked,
swapped_out_waiting), to_state=waiting)
run = Event(from_states=waiting, to_state=running)
terminate = Event(from_states=running, to_state=terminated)
block = Event(from_states=(running, swapped_out_blocked),
to_state=blocked)
swap_wait = Event(from_states=waiting, to_state=swapped_out_waiting)
swap_block = Event(from_states=blocked, to_state=swapped_out_blocked)

每个进程都有一个名称。正式的应用场景中,一个进程需要多得多的信息才能发挥其作用(例如, ID、优先级和状态等),但为了专注于模式本身,我们进行一些简化。

    def __init__(self, name):
self.name = name

在发生状态转换时,如果什么影响都没有,那转换就没什么用了。 state_machine模块提供@before和@after修饰器,用于在状态转换之前或之后执行动作。为了达到示例的目的,这里的动作限于输出进程状态转换的信息。

    @after('wait')
def wait_info(self):
print('{} entered waiting mode'.format(self.name))
@after('run')
def run_info(self):
print('{} is running'.format(self.name))
@before('terminate')
def terminate_info(self):
print('{} terminated'.format(self.name))
@after('block')
def block_info(self):
print('{} is blocked'.format(self.name))
@after('swap_wait')
def swap_wait_info(self):
print('{} is swapped out and waiting'.format(self.name))
@after('swap_block')
def swap_block_info(self):
print('{} is swapped out and blocked'.format(self.name))

transition()函数接受三个参数: process、 event和event_name。 process是一个Process类实例, event是一个Event类( wait、 run和terminate等)实例,而event_name是事件的名称。在尝试执行event时,如果发生错误,则会输出事件的名称。

def transition(process, event, event_name):
try:
event()
except InvalidStateTransition as err:
print('Error: transition of {} from {} to {} failed'.format(process.name,process.current_state, event_name))

state_info()函数展示进程当前(激活)状态的一些基本信息。

def state_info(process):
print('state of {}: {}'.format(process.name, process.current_state))

在main()函数的开始,我们定义了一些字符串常量,作为event_name参数值传递。

def main():
RUNNING = 'running'
WAITING = 'waiting'
BLOCKED = 'blocked'
TERMINATED = 'terminated'

接着,我们创建两个Process实例,并输出它们的初始状态信息。

p1, p2 = Process('process1'), Process('process2')
[state_info(p) for p in (p1, p2)]

函数的其余部分将尝试不同的状态转换。回忆一下本章之前提到的状态图。允许的状态转换应与状态图一致。例如,从状态“运行”转换到状态“阻塞”是可能的,但从状态“阻塞”转换到状态“运行”则是不可能的。

print()
transition(p1, p1.wait, WAITING)
transition(p2, p2.terminate, TERMINATED)
[state_info(p) for p in (p1, p2)]
print()
transition(p1, p1.run, RUNNING)
transition(p2, p2.wait, WAITING)
[state_info(p) for p in (p1, p2)]
print()
transition(p2, p2.run, RUNNING)
[state_info(p) for p in (p1, p2)]
print()
[transition(p, p.block, BLOCKED) for p in (p1, p2)]
[state_info(p) for p in (p1, p2)]
print()
[transition(p, p.terminate, TERMINATED) for p in (p1, p2)]
[state_info(p) for p in (p1, p2)]

下面是完整代码:

from state_machine import State, Event, acts_as_state_machine, after, before,
InvalidStateTransition
@acts_as_state_machine
class Process:
created = State(initial=True)
waiting = State()
running = State()
terminated = State()
blocked = State()
swapped_out_waiting = State()
swapped_out_blocked = State()
wait = Event(from_states=(created, running, blocked,
swapped_out_waiting), to_state=waiting)
run = Event(from_states=waiting, to_state=running)
terminate = Event(from_states=running, to_state=terminated)
block = Event(from_states=(running, swapped_out_blocked),
to_state=blocked)
swap_wait = Event(from_states=waiting, to_state=swapped_out_waiting)
swap_block = Event(from_states=blocked, to_state=swapped_out_blocked)
def __init__(self, name):
self.name = name
@after('wait')
def wait_info(self):
print('{} entered waiting mode'.format(self.name))
@after('run')
def run_info(self):
print('{} is running'.format(self.name))
@before('terminate')
def terminate_info(self):
print('{} terminated'.format(self.name))
@after('block')
def block_info(self):
print('{} is blocked'.format(self.name))
@after('swap_wait')
def swap_wait_info(self):
print('{} is swapped out and waiting'.format(self.name))
@after('swap_block')
def swap_block_info(self):
print('{} is swapped out and blocked'.format(self.name))
def transition(process, event, event_name):
try:
event()
except InvalidStateTransition as err:
print('Error: transition of {} from {} to {} failed'.format(process.name,process.current_state, event_name))
def state_info(process):
print('state of {}: {}'.format(process.name, process.current_state))
def main():
RUNNING = 'running'
WAITING = 'waiting'
BLOCKED = 'blocked'
TERMINATED = 'terminated'
p1, p2 = Process('process1'), Process('process2')
[state_info(p) for p in (p1, p2)]
print()
transition(p1, p1.wait, WAITING)
transition(p2, p2.terminate, TERMINATED)
[state_info(p) for p in (p1, p2)]
print()
transition(p1, p1.run, RUNNING)
transition(p2, p2.wait, WAITING)
[state_info(p) for p in (p1, p2)]
print()
transition(p2, p2.run, RUNNING)
[state_info(p) for p in (p1, p2)]
print()
[transition(p, p.block, BLOCKED) for p in (p1, p2)]
[state_info(p) for p in (p1, p2)]
print()
[transition(p, p.terminate, TERMINATED) for p in (p1, p2)]
[state_info(p) for p in (p1, p2)]
if __name__ == '__main__':
main()

输出:

state of process1: created
state of process2: created
process1 entered waiting mode
Error: transition of process2 from created to terminated failed
state of process1: waiting
state of process2: created
process1 is running
process2 entered waiting mode
state of process1: running
state of process2: waiting
process2 is running
state of process1: running
state of process2: running
process1 is blocked
process2 is blocked
state of process1: blocked
state of process2: blocked
Error: transition of process1 from blocked to terminated failed
Error: transition of process2 from blocked to terminated failed
state of process1: blocked
state of process2: blocked

确实,输出内容显示,非法的状态转换(比如,“已创建” →“终止”和“阻塞” →“终止”)都失败了。我们不希望应用在请求一个非法转换时崩溃,而except代码块能正确地处理这一点。

注意如何使用stat_machine这样的一个好模块来消除条件式逻辑。没有必要使用冗长易错的if-else语句来检测每个状态转换并作出反应。

5. 小结

状态机是一个抽象机器,具有两个主要部分:状态和转换。状态是指一个系统的当前状况。一个状态机在任意时间点只会有一个激活状态。转换是指从当前状态到一个新状态的切换。在一个转换发生之前或之后通常会执行一个或多个动作。状态机可以使用状态图进行视觉上的展现。

状态机用于解决许多计算机问题和非计算机问题,其中包括交通灯、停车计时器、硬件设计和编程语言解析等。我们也看到零食自动贩卖机是如何与状态机的工作方式相关联的。

许多现代软件提供库/模块来简化状态机的实现与使用。 Django提供第三方包django-fsm,Python也有许多大家贡献的模块。实际上,在14.4节就使用了其中的一个模块( state_machine)。状态机编译器是另一个有前景的项目,提供许多编程语言的绑定(包括Python)。

python设计模式之状态模式的更多相关文章

  1. python 设计模式之状态模式

    1.为什么会出现状态模式? 在软件开发过程中,各种应用程序可能会根据不同的情况做出不同的处理.最直接的方案就是把所有的可能发生的情况都考虑到.然后使用条件语句(if...elseif...elseif ...

  2. Python设计模式(11)-状态模式

    # coding=utf-8 # *状态模式:一个方法的判断逻辑太长,就不容易修改.方法过长,其本质就是,# * 就是本类在不同条件下的状态转移.状态模式,就是将这些判断分开到各个能# * 表示当前状 ...

  3. python设计模式之解释器模式

    python设计模式之解释器模式 对每个应用来说,至少有以下两种不同的用户分类. [ ] 基本用户:这类用户只希望能够凭直觉使用应用.他们不喜欢花太多时间配置或学习应用的内部.对他们来说,基本的用法就 ...

  4. python设计模式之命令模式

    python设计模式之命令模式 现在多数应用都有撤销操作.虽然难以想象,但在很多年里,任何软件中确实都不存在撤销操作.撤销操作是在1974年引入的,但Fortran和Lisp分别早在1957年和195 ...

  5. python设计模式之外观模式

    python设计模式之外观模式 系统会随着演化变得非常复杂,最终形成大量的(并且有时是令人迷惑的)类和交互,这种情况并不少见.许多情况下,我们并不想把这种复杂性暴露给客户端.外观设计模式有助于隐藏系统 ...

  6. 【转】设计模式 ( 十七) 状态模式State(对象行为型)

    设计模式 ( 十七) 状态模式State(对象行为型) 1.概述 在软件开发过程中,应用程序可能会根据不同的情况作出不同的处理.最直接的解决方案是将这些所有可能发生的情况全都考虑到.然后使用if... ...

  7. 设计模式 ( 十七) 状态模式State(对象行为型)

    设计模式 ( 十七) 状态模式State(对象行为型) 1.概述 在软件开发过程中,应用程序可能会根据不同的情况作出不同的处理.最直接的解决方案是将这些所有可能发生的情况全都考虑到.然后使用if... ...

  8. 乐在其中设计模式(C#) - 状态模式(State Pattern)

    原文:乐在其中设计模式(C#) - 状态模式(State Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 状态模式(State Pattern) 作者:webabcd 介绍 允 ...

  9. 折腾Java设计模式之状态模式

    原文地址 折腾Java设计模式之状态模式 状态模式 在状态模式(State Pattern)中,类的行为是基于它的状态改变的.这种类型的设计模式属于行为型模式.在状态模式中,我们创建表示各种状态的对象 ...

随机推荐

  1. 【Python学习笔记一】基础环境安装:idea+python

    IDEA 安装 1.下载IDEA   官网下载地址: https://www.jetbrains.com/idea/ 2.安装的时候配置基本选择默认配置就行 参考链接:https://blog.csd ...

  2. Java对象创建模式

    创建Java对象时,对于可为空的属性,创建对象的时候有3种模式:重叠构造器模式.JavaBeans模式.Builder模式(推荐).Stream模式(推荐).                     ...

  3. 利用cublasHgemm来实现cublasHgemv

    前几天做half量化时发现cublas竟然没有提供half版本的矩阵-向量乘,也就是half版本的cublasHgemv.自己写一个又太麻烦,重点是精度和耗时不一定比cublas提供的要好,不过cub ...

  4. python监控服务器应用日志,推送钉钉机器人,实时关注日志异常

    生产环境多台服务器上部署了多个应用,日志出现报错时,无法及时反馈到开发人员.部署一个大型的运维监控应用,不但耗资源,而且配置也不简单. 简简单单写个python脚本来监控服务器日志就简单多了,废话不多 ...

  5. 微信小程序反编译~2020年

    目录 摘要 介绍 安装反编译脚本 使用 获取wxapkg文件 反编译 结论 参考资料 摘要 安装wxappUnpacker小程序反编译工具并使用(2020.03) 关键词: 微信小程序反编译 wxss ...

  6. ES6面试

    未完持续 概念 ECMAScript6(以下简称ES6)是 JavaScript 语言的下一代标准,前者是后者的规格,后者是前者的一种实现. ES6(新增的)一些特性 1.变.常量:let声明变量,c ...

  7. react native redux

    redux可以解决, 程序中所有组件的状态统一管理, 从而使我们可以更加动态的,灵活的控制程序 React:数据管理使用props.stateRedux的主要思想:提供一个数据存储中心,可以供外部访问 ...

  8. ICPC North Central NA Contest 2018

    目录 ICPC North Central NA Contest 2018 1. 题目分析 2. 题解 A.Pokegene B.Maximum Subarrays C.Rational Ratio ...

  9. Pexpect模块的简单使用

    Pexpect 是一个用来启动子程序并对其进行自动控制的 Python 模块. Pexpect 可以用来和像 ssh.ftp.passwd.telnet 等命令行程序进行自动交互.以下所有代码都是在K ...

  10. log4j2.xml配置使用

    jar包: log4j-api-2.10.0.jar log4j-core-2.10.10.jar log4j-1.2-api-2.10.0.jar log4j-slf4j-impl-2.10.10. ...