Python设计模式之状态模式(14)

状态模式(The State Pattern):实现有限的状态机,类的行为是基于它的状态改变的。是一种行为设计模式, 让你能在一个对象的内部状态变化时改变其行为, 使其看上去就像改变了自身所属的类一样。
在状态模式中,我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的 context 对象。
其主要思想是程序在任意时刻仅可处于几种有限的状态中。 在任何一个特定状态中, 程序的行为都不相同, 且可瞬间从一个状态切换到另一个状态。 不过, 根据当前状态, 程序可能会切换到另外一种状态, 也可能会保持当前状态不变。 这些数量有限且预先定义的状态切换规则被称为转移。

1 介绍

  • 意图:允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。
  • 主要解决:对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为。
  • 何时使用:代码中包含大量与对象状态有关的条件语句。
    如何解决:将各种具体的状态类抽象出来。
  • 关键代码:通常命令模式的接口中只有一个方法。而状态模式的接口中有一个或者多个方法。而且,状态模式的实现类的方法,一般返回值,或者是改变实例变量的值。也就是说,状态模式一般和对象的状态有关。实现类的方法有不同的功能,覆盖接口中的方法。状态模式和命令模式一样,也可以用于消除 if…else 等条件选择语句。
  • 优点: 1、 封装了转换规则。 2、枚举可能的状态,在枚举状态之前需要确定状态种类。 3、将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。 4、允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。 5、可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
  • 缺点: 1、状态模式的使用必然会增加系统类和对象的个数。 2、状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。 3、状态模式对"开闭原则"的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。
  • 使用场景: 1、行为随状态改变而改变的场景。 2、条件、分支语句的代替者。
  • 注意事项:在行为受状态约束的时候使用状态模式,而且状态不超过 5 个

2 适用场景

  • 如果对象需要根据自身当前状态进行不同行为, 同时状态的数量非常多且与状态相关的代码会频繁变更的话, 可使用状态模式。
    模式建议你将所有特定于状态的代码抽取到一组独立的类中。 这样一来, 你可以在独立于其他状态的情况下添加新状态或修改已有状态, 从而减少维护成本。
  • 如果某个类需要根据成员变量的当前值改变自身行为, 从而需要使用大量的条件语句时, 可使用该模式。
    状态模式会将这些条件语句的分支抽取到相应状态类的方法中。 同时, 你还可以清除主要类中与特定状态相关的临时成员变量和帮手方法代码。
  • 当相似状态和基于条件的状态机转换中存在许多重复代码时, 可使用状态模式。
    状态模式让你能够生成状态类层次结构, 通过将公用代码抽取到抽象基类中来减少重复。

3 使用步骤

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

假如你有一个 文档Document类。 文档可能会处于 草稿Draft 、 ​ 审阅中Moderation和 已发布Published三种状态中的一种。 文档的 publish发布方法在不同状态下的行为略有不同:

  • 处于 草稿状态时, 它会将文档转移到审阅中状态。
  • 处于 审阅中状态时, 如果当前用户是管理员, 它会公开发布文档。
  • 处于 已发布状态时,它不会进行任何操作。
    在这里插入图片描述
    状态模式建议为对象的所有可能状态新建一个类, 然后将所有状态的对应行为抽取到这些类中。
    原始对象被称为上下文 (context), 它并不会自行实现所有行为, 而是会保存一个指向表示当前状态的状态对象的引用, 且将所有与状态相关的工作委派给该对象。

在这里插入图片描述
如需将上下文转换为另外一种状态, 则需将当前活动的状态对象替换为另外一个代表新状态的对象。 采用这种方式是有前提的: 所有状态类都必须遵循同样的接口, 而且上下文必须仅通过接口与这些对象进行交互。
这个结构可能看上去与策略模式相似, 但有一个关键性的不同——在状态模式中, 特定状态知道其他所有状态的存在, 且能触发从一个状态到另一个状态的转换; 策略则几乎完全不知道其他策略的存在。

状态模式结构
模式结构
实现方式

(1) 确定哪些类是上下文。 它可能是包含依赖于状态的代码的已有类; 如果特定于状态的代码分散在多个类中, 那么它可能是一个新的类。
(2)声明状态接口。 虽然你可能会需要完全复制上下文中声明的所有方法, 但最好是仅把关注点放在那些可能包含特定于状态的行为的方法上。
(3)为每个实际状态创建一个继承于状态接口的类。 然后检查上下文中的方法并将与特定状态相关的所有代码抽取到新建的类中。
在将代码移动到状态类的过程中, 你可能会发现它依赖于上下文中的一些私有成员。 你可以采用以下几种变通方式:

  • 将这些成员变量或方法设为公有。
  • 将需要抽取的上下文行为更改为上下文中的公有方法, 然后在状态类中调用。 这种方式简陋却便捷, 你可以稍后再对其进行修补。
  • 将状态类嵌套在上下文类中。 这种方式需要你所使用的编程语言支持嵌套类。

(5)在上下文类中添加一个状态接口类型的引用成员变量, 以及一个用于修改该成员变量值的公有设置器。
(6)再次检查上下文中的方法, 将空的条件语句替换为相应的状态对象方法。
(7) 为切换上下文状态, 你需要创建某个状态类实例并将其传递给上下文。 你可以在上下文、 各种状态或客户端中完成这项工作。 无论在何处完成这项工作, 该类都将依赖于其所实例化的具体类。

4 代码示例

概念示例

from abc import ABC, abstractmethod

class Context(ABC):
    """
    Contex类,是client端调用的入口。该类需要接收State对象,指示当前State状态的上下文,并且维护各种State对象的引用
    """
    _state = None

    def __init__(self, state):
        """接收state对象,并且指向该对象"""
        self.transition_to(state)

    def transition_to(self, state):
        """程序运行中切换state对象, 将输入对象的上下文切换为当前state对象"""
        print(f"Context: Transition to {type(state).__name__}")
        self._state = state
        self._state.context = self

    ##当前对象的行为
    def request1(self):
        self._state.handle1()

    def request2(self):
        self._state.handle2()

class State(ABC):
    """State抽象类,声明各个子类需要实现的方法。并且提供到Context对象的回溯引用,该引用可用来根据context上下文切换到指定的state"""
    @property
    def context(self):
        return self._context

    @context.setter
    def context(self, context):
        self._context = context

    @abstractmethod
    def handle1(self) -> None:
        pass

    @abstractmethod
    def handle2(self) -> None:
        pass


class StateA(State):
    def handle1(self) -> None:
        print(f"{type(self).__name__} handle request1")
        print(f"{type(self).__name__} wants to change the state of the context.")
        self.context.transition_to(StateB())

    def handle2(self) -> None:
        print(f"{type(self).__name__} handle request2")

class StateB(State):
    def handle1(self) -> None:
        print(f"{type(self).__name__} handle request1")
        
    def handle2(self) -> None:
        print(f"{type(self).__name__} handle request2")
        print(f"{type(self).__name__} wants to change the state of the context.")
        self.context.transition_to(StateA())


if __name__ == "__main__":
    context = Context(StateA()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值