Python 状态模式

状态模式的核心思想是将一个对象的状态封装成独立的类,并将对象的行为委托给当前状态对象。当对象的状态发生变化时,它会切换到不同的状态对象,从而改变其行为。

为什么需要状态模式?

在没有使用状态模式的情况下,我们通常会在一个类中使用大量的 if-elseswitch-case 语句来处理不同状态下的行为。这种方式存在几个问题:

  • 代码臃肿:随着状态数量的增加,条件判断语句会变得越来越复杂
  • 难以维护:修改某个状态的行为可能会影响其他状态
  • 违反开闭原则:添加新状态需要修改现有代码

状态模式通过将每个状态封装成独立的类来解决这些问题。


状态模式的结构

主要角色

状态模式包含三个核心角色:

  1. Context(上下文):维护一个具体状态对象的实例,这个实例定义当前状态
  2. State(状态接口):定义一个接口,用于封装与 Context 的特定状态相关的行为
  3. ConcreteState(具体状态):实现 State 接口,每一个具体状态类实现一个与 Context 的状态相关的行为

UML 类图


状态模式的实现

基本实现步骤

让我们通过一个简单的例子来理解状态模式的实现。假设我们有一个电梯系统,电梯有不同的状态:开门、关门、运行、停止。

步骤 1:定义状态接口

实例

from abc import ABC, abstractmethod

class ElevatorState(ABC):
    """电梯状态接口"""
   
    @abstractmethod
    def open_doors(self):
        """开门操作"""
        pass
   
    @abstractmethod
    def close_doors(self):
        """关门操作"""
        pass
   
    @abstractmethod
    def move(self):
        """移动操作"""
        pass
   
    @abstractmethod
    def stop(self):
        """停止操作"""
        pass

步骤 2:实现具体状态类

实例

class DoorOpenState(ElevatorState):
    """开门状态"""
   
    def open_doors(self):
        print("门已经是开着的")
        return self
   
    def close_doors(self):
        print("正在关门...")
        return DoorClosedState()
   
    def move(self):
        print("错误:门还开着,不能移动")
        return self
   
    def stop(self):
        print("电梯已经停止")
        return self

class DoorClosedState(ElevatorState):
    """关门状态"""
   
    def open_doors(self):
        print("正在开门...")
        return DoorOpenState()
   
    def close_doors(self):
        print("门已经是关着的")
        return self
   
    def move(self):
        print("电梯开始移动...")
        return MovingState()
   
    def stop(self):
        print("电梯已经停止")
        return self

class MovingState(ElevatorState):
    """移动状态"""
   
    def open_doors(self):
        print("错误:电梯在移动中,不能开门")
        return self
   
    def close_doors(self):
        print("门已经是关着的")
        return self
   
    def move(self):
        print("电梯正在移动中")
        return self
   
    def stop(self):
        print("电梯停止中...")
        return DoorClosedState()

步骤 3:创建上下文类

实例

class Elevator:
    """电梯上下文类"""
   
    def __init__(self):
        # 初始状态为门关闭状态
        self._state = DoorClosedState()
   
    @property
    def state(self):
        """获取当前状态"""
        return self._state
   
    def set_state(self, state):
        """设置新状态"""
        self._state = state
        print(f"状态已切换为: {state.__class__.__name__}")
   
    def open_doors(self):
        """开门操作"""
        print("执行开门操作...")
        self.set_state(self._state.open_doors())
   
    def close_doors(self):
        """关门操作"""
        print("执行关门操作...")
        self.set_state(self._state.close_doors())
   
    def move(self):
        """移动操作"""
        print("执行移动操作...")
        self.set_state(self._state.move())
   
    def stop(self):
        """停止操作"""
        print("执行停止操作...")
        self.set_state(self._state.stop())

步骤 4:使用状态模式

实例

# 创建电梯实例
elevator = Elevator()

# 测试状态转换
print("=== 电梯状态转换测试 ===")

elevator.open_doors()   # 从关门状态切换到开门状态
elevator.close_doors()  # 从开门状态切换回关门状态
elevator.move()         # 从关门状态切换到移动状态
elevator.stop()         # 从移动状态切换回关门状态

print("\n=== 测试非法操作 ===")
elevator.open_doors()   # 正常开门
elevator.move()         # 尝试在开门状态下移动(应该报错)

运行结果

=== 电梯状态转换测试 ===
执行开门操作...
正在开门...
状态已切换为: DoorOpenState
执行关门操作...
正在关门...
状态已切换为: DoorClosedState
执行移动操作...
电梯开始移动...
状态已切换为: MovingState
执行停止操作...
电梯停止中...
状态已切换为: DoorClosedState

=== 测试非法操作 ===
执行开门操作...
正在开门...
状态已切换为: DoorOpenState
执行移动操作...
错误:门还开着,不能移动
状态已切换为: DoorOpenState

状态模式的进阶应用

带有共享状态的状态模式

在某些情况下,多个上下文可能需要共享相同的状态对象。我们可以通过单例模式来实现状态的共享。

实例

class Singleton(type):
    """单例元类"""
    _instances = {}
   
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super().__call__(*args, **kwargs)
        return cls._instances[cls]

class SharedDoorOpenState(ElevatorState, metaclass=Singleton):
    """共享的开门状态(单例)"""
   
    def open_doors(self):
        print("门已经是开着的")
        return self
   
    def close_doors(self):
        print("正在关门...")
        return SharedDoorClosedState()
   
    def move(self):
        print("错误:门还开着,不能移动")
        return self
   
    def stop(self):
        print("电梯已经停止")
        return self

class SharedDoorClosedState(ElevatorState, metaclass=Singleton):
    """共享的关门状态(单例)"""
   
    def open_doors(self):
        print("正在开门...")
        return SharedDoorOpenState()
   
    def close_doors(self):
        print("门已经是关着的")
        return self
   
    def move(self):
        print("电梯开始移动...")
        return SharedMovingState()
   
    def stop(self):
        print("电梯已经停止")
        return self

class SharedMovingState(ElevatorState, metaclass=Singleton):
    """共享的移动状态(单例)"""
   
    def open_doors(self):
        print("错误:电梯在移动中,不能开门")
        return self
   
    def close_doors(self):
        print("门已经是关着的")
        return self
   
    def move(self):
        print("电梯正在移动中")
        return self
   
    def stop(self):
        print("电梯停止中...")
        return SharedDoorClosedState()

状态模式与状态机

状态模式经常与状态机结合使用。下面是一个更复杂的状态机示例:

实例

class VendingMachineState(ABC):
    """自动售货机状态接口"""
   
    @abstractmethod
    def insert_coin(self, machine, amount):
        """投币"""
        pass
   
    @abstractmethod
    def select_product(self, machine, product):
        """选择商品"""
        pass
   
    @abstractmethod
    def dispense(self, machine):
        """出货"""
        pass
   
    @abstractmethod
    def refund(self, machine):
        """退款"""
        pass

class WaitingState(VendingMachineState):
    """等待投币状态"""
   
    def insert_coin(self, machine, amount):
        print(f"投入 {amount} 元")
        machine.balance += amount
        return HasMoneyState()
   
    def select_product(self, machine, product):
        print("请先投币")
        return self
   
    def dispense(self, machine):
        print("请先投币并选择商品")
        return self
   
    def refund(self, machine):
        print("没有可退款的金额")
        return self

class HasMoneyState(VendingMachineState):
    """已投币状态"""
   
    def insert_coin(self, machine, amount):
        print(f"继续投入 {amount} 元")
        machine.balance += amount
        return self
   
    def select_product(self, machine, product):
        if product.price <= machine.balance:
            print(f"已选择商品: {product.name}")
            machine.selected_product = product
            return ProductSelectedState()
        else:
            print("余额不足,请继续投币或选择更便宜的商品")
            return self
   
    def dispense(self, machine):
        print("请先选择商品")
        return self
   
    def refund(self, machine):
        print(f"退款 {machine.balance} 元")
        machine.balance = 0
        return WaitingState()

class ProductSelectedState(VendingMachineState):
    """商品已选择状态"""
   
    def insert_coin(self, machine, amount):
        print("商品已选择,无法继续投币")
        return self
   
    def select_product(self, machine, product):
        print("商品已选择,请先完成当前交易")
        return self
   
    def dispense(self, machine):
        product = machine.selected_product
        change = machine.balance - product.price
       
        print(f"出货: {product.name}")
        if change > 0:
            print(f"找零: {change} 元")
       
        machine.balance = 0
        machine.selected_product = None
        return WaitingState()
   
    def refund(self, machine):
        print(f"退款 {machine.balance} 元")
        machine.balance = 0
        machine.selected_product = None
        return WaitingState()

class Product:
    """商品类"""
   
    def __init__(self, name, price):
        self.name = name
        self.price = price

class VendingMachine:
    """自动售货机"""
   
    def __init__(self):
        self._state = WaitingState()
        self.balance = 0
        self.selected_product = None
   
    def set_state(self, state):
        self._state = state
   
    def insert_coin(self, amount):
        print(f"操作: 投币 {amount} 元")
        self.set_state(self._state.insert_coin(self, amount))
   
    def select_product(self, product):
        print(f"操作: 选择商品 {product.name}")
        self.set_state(self._state.select_product(self, product))
   
    def dispense(self):
        print("操作: 请求出货")
        self.set_state(self._state.dispense(self))
   
    def refund(self):
        print("操作: 请求退款")
        self.set_state(self._state.refund(self))

状态模式的优缺点

优点

  1. 单一职责原则:将与特定状态相关的代码放在独立的类中
  2. 开闭原则:无需修改已有状态类和上下文就能引入新状态
  3. 消除条件语句:通过多态调用消除庞大的条件分支语句
  4. 状态转换明确:使状态转换更加明确,减少因状态转换导致的错误

缺点

  1. 可能过度设计:如果状态数量很少或很少改变,使用状态模式可能会过度复杂
  2. 状态类数量增加:每个状态都需要一个对应的类,可能导致类数量增加
  3. 上下文与状态耦合:上下文需要了解所有具体状态类,以便进行状态转换

实践练习

练习 1:改进电梯系统

尝试为电梯系统添加以下功能:

  • 添加"维修中"状态,在该状态下所有操作都被禁止
  • 添加楼层选择功能,只有在停止状态才能选择楼层
  • 实现楼层到达的自动状态转换

练习 2:实现交通灯系统

使用状态模式实现一个交通灯系统,包含红灯、绿灯、黄灯三种状态,状态转换规则如下:

  • 红灯 → 绿灯(30秒后)
  • 绿灯 → 黄灯(25秒后)
  • 黄灯 → 红灯(5秒后)

练习 3:游戏角色状态

设计一个游戏角色的状态系统,包含以下状态:

  • 正常状态:可以移动、攻击
  • 中毒状态:持续扣血,移动速度减半
  • 眩晕状态:无法移动和攻击
  • 无敌状态:不受伤害

总结

状态模式是一个强大的设计模式,特别适合处理对象行为依赖于其状态,且状态数量较多、状态转换复杂的场景。通过将每个状态封装成独立的类,状态模式使得代码更加清晰、易于维护和扩展。

关键要点

  • 状态模式通过将状态封装为对象来消除条件判断
  • 上下文将状态相关的行为委托给当前状态对象
  • 状态转换可以由上下文或状态对象自身触发
  • 合理使用状态模式可以大大提高代码的可维护性