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. 手把手教你安装Office 2019 for Mac ,安装包和破解码都给你准备好了,还装不上的话,你找我!

    准备一个安装包,和一个破解工具 ​ 安装MicrosoftOffice16.23.19030902_Installer.pkg, 注意在断网情况下安装 同时不要自动更新 , 安装好之后不要打开文件!​ ...

  2. antd4 源码学习 :表单

    Evernote Export 首先.vue 的数据流是双向的,而 react 的数据流是单向的. 这意味着什么? 这意味着,vue 中,子组件可以用 emit 把数据更新传给父组件.而 react ...

  3. 数据结构 | 30行代码,手把手带你实现Trie树

    本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是算法和数据结构专题的第28篇文章,我们一起来聊聊一个经典的字符串处理数据结构--Trie. 在之前的4篇文章当中我们介绍了关于博弈论的 ...

  4. 设计模式:decorator模式

    两点: 继承同一虚接口,实现数据一致性 桥接方式指向被装饰类 目的:在不改变被装饰类功能的前提下增加新功能 特点:继承是子类和父类强耦合,桥接是低耦合 例子: class Print //抽象接口 { ...

  5. Python基础点记录2

    ---- PygLatin 1 介绍函数的调用,就是直接函数名 def square(n): squared = n**2 print "%d squared is %d." % ...

  6. Just test it!!软件测试测起来!!

    (图片: josh@unsplash,字数:700,时间:1分钟) (一) 一切的软件质量保障活动,归根结底,就两种类型. 一种是基于代码执行的,一种是不基于代码执行的. 测试之于肉眼自查.静态检查. ...

  7. WBF交易所如何使用二次验证码/谷歌身份验证器

    一般点账户名——设置——安全设置中开通虚拟MFA两步验证 具体步骤见链接  WBF交易所如何使用二次验证码/谷歌身份验证器 二次验证码小程序于谷歌身份验证器APP的优势 1.无需下载app 2.验证码 ...

  8. web自动化 -- 框架

    一.框架源码 https://github.com/jiangnan27/Autotest_UI_Open   二.框架大概介绍 Python3 + selenium3 + pytest5.3 + a ...

  9. Java8线程池ThreadPoolExecutor底层原理及其源码解析

    小侃一下 日常开发中, 或许不会直接new线程或线程池, 但这些线程相关的基础或思想是非常重要的, 参考林迪效应; 就算没有直接用到, 可能间接也用到了类似的思想或原理, 例如tomcat, jett ...

  10. Java-旋转字符串

    描述 旋转字符串 给定一个字符串(以字符数组的形式给出)和一个偏移量,根据偏移量原地旋转字符串(从左向右旋转). 挑战 在数组上原地旋转,使用O(1)的额外空间 说明 原地旋转意味着你要在s本身进行修 ...