Python 状态模式
状态模式的核心思想是将一个对象的状态封装成独立的类,并将对象的行为委托给当前状态对象。当对象的状态发生变化时,它会切换到不同的状态对象,从而改变其行为。
为什么需要状态模式?
在没有使用状态模式的情况下,我们通常会在一个类中使用大量的 if-else 或 switch-case 语句来处理不同状态下的行为。这种方式存在几个问题:
- 代码臃肿:随着状态数量的增加,条件判断语句会变得越来越复杂
- 难以维护:修改某个状态的行为可能会影响其他状态
- 违反开闭原则:添加新状态需要修改现有代码
状态模式通过将每个状态封装成独立的类来解决这些问题。
状态模式的结构
主要角色
状态模式包含三个核心角色:
- Context(上下文):维护一个具体状态对象的实例,这个实例定义当前状态
- State(状态接口):定义一个接口,用于封装与 Context 的特定状态相关的行为
- 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
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()
"""开门状态"""
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())
"""电梯上下文类"""
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() # 尝试在开门状态下移动(应该报错)
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()
"""单例元类"""
_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))
"""自动售货机状态接口"""
@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:实现交通灯系统
使用状态模式实现一个交通灯系统,包含红灯、绿灯、黄灯三种状态,状态转换规则如下:
- 红灯 → 绿灯(30秒后)
- 绿灯 → 黄灯(25秒后)
- 黄灯 → 红灯(5秒后)
练习 3:游戏角色状态
设计一个游戏角色的状态系统,包含以下状态:
- 正常状态:可以移动、攻击
- 中毒状态:持续扣血,移动速度减半
- 眩晕状态:无法移动和攻击
- 无敌状态:不受伤害
总结
状态模式是一个强大的设计模式,特别适合处理对象行为依赖于其状态,且状态数量较多、状态转换复杂的场景。通过将每个状态封装成独立的类,状态模式使得代码更加清晰、易于维护和扩展。
关键要点:
- 状态模式通过将状态封装为对象来消除条件判断
- 上下文将状态相关的行为委托给当前状态对象
- 状态转换可以由上下文或状态对象自身触发
- 合理使用状态模式可以大大提高代码的可维护性
点我分享笔记