Python 设计模式——中介者模式(Mediator Pattern)详解
本文面向零基础新手,从概念到“多对象交互集中化”,从聊天室到表单协调,全方位讲解中介者模式。
一、什么是中介者模式?
1.1 通俗理解
中介者模式是一种行为型设计模式,核心思想是:
多个对象之间要相互通信、协作,若每个对象都直接引用其他对象,会形成“网状依赖”,难以维护和扩展。中介者模式引入一个“中介者”对象:所有对象只和中介者通信,由中介者负责把消息转给谁、怎么协调;这样对象之间不再直接引用,交互逻辑集中在中介者里,耦合降低。
可以这样类比:
- 不用中介者:聊天室里 10 个用户,若每个用户都持有另外 9 个人的引用,谁发消息就循环调另外 9 个人的“收消息”方法。加一个用户要改 10 处,谁能收谁不能收的逻辑也散落各处。
- 用中介者:有一个“聊天室”(中介者);每个用户只认识聊天室,发消息时调“聊天室.发消息(我, 内容)”;聊天室负责把消息转给其他人(或按规则过滤、广播)。用户之间互不引用,只和聊天室打交道,加人、改规则只改聊天室。
所以,中介者模式解决的是:把“多对象之间的复杂交互”收拢到中介者里,让对象只和中介者通信,从而降低对象间的耦合、便于修改交互规则。
1.2 为什么需要中介者?
场景一:对象很多、两两都要交互
若 N 个对象两两都可能通信,直接引用会导致 N×(N-1) 条依赖,改一个牵动很多。用中介者后,每个对象只依赖中介者,交互规则在中介者里集中维护。
场景二:交互规则经常变
例如:谁的消息能发给谁、什么条件下触发谁。若规则写在各个对象里,改规则要改多处;集中到中介者后,只改中介者即可。
场景三:复用“单个对象”困难
若对象 A 直接依赖 B、C、D,把 A 拿到别的项目里就要带上 B、C、D;若 A 只依赖中介者,只要提供合适的中介者,A 就可以复用。
1.3 适用场景(什么时候用?)
| 场景 | 说明 |
|---|---|
| 多对象复杂交互 | 聊天室、表单里多个控件、订单-库存-支付-物流 等需要协调。 |
| 交互规则集中管理 | 谁通知谁、什么条件下触发谁,希望在一处维护。 |
| 降低对象间引用 | 对象只和中介者通信,不直接引用其他业务对象。 |
简单记忆:多个对象要互相通信、协作,且希望交互逻辑集中、对象间少直接引用时,用中介者模式。
二、中介者模式的结构(两个角色)
| 角色 | 说明 |
|---|---|
| 中介者(Mediator) | 定义“同事对象”之间通信的接口(如发送消息、通知某对象);通常持有一份“同事”的列表或映射,负责把请求转给谁、怎么转。 |
| 同事(Colleague) | 参与协作的各个对象;每个同事持有对中介者的引用,需要和其他对象协作时,不直接找对方,而是通过中介者(如 mediator.notify(other, msg) 或 mediator.send(from, to, msg))。 |
关系可以理解为:
- 同事 A 想通知 同事 B:A 不直接调 B,而是调 中介者(如“聊天室.send(我, 内容)”);中介者再决定把消息给谁(所有人、或指定人、或按规则过滤)。
- 中介者 知道所有同事(或能根据参数找到目标),负责转发、协调、可能改变行为(如权限、日志、广播)。
三、示例一:聊天室(最经典)
需求:多个用户在一个聊天室里;用户发消息时,不直接发给其他人,而是发给“聊天室”,由聊天室把消息广播给除自己外的所有人。用户之间互不引用。
3.1 中介者:聊天室
from abc import ABC, abstractmethod
class Mediator(ABC):
@abstractmethod
def send(self, sender, message: str):
pass
class ChatRoom(Mediator):
"""中介者:聊天室,持有所有用户,负责广播"""
def __init__(self):
self._users = []
def add_user(self, user: "User"):
self._users.append(user)
def send(self, sender, message: str):
# 广播给除发送者外的所有人
for u in self._users:
if u != sender:
u.receive(sender, message)
3.2 同事:用户(只依赖中介者)
class User:
"""同事:用户,只持有中介者(聊天室),不持有其他用户"""
def __init__(self, name: str, mediator: Mediator):
self.name = name
self._mediator = mediator
def say(self, message: str):
self._mediator.send(self, message)
def receive(self, sender, message: str):
print(f" [{self.name}] 收到 {sender.name} 的消息: {message}")
3.3 使用
room = ChatRoom()
alice = User("Alice", room)
bob = User("Bob", room)
room.add_user(alice)
room.add_user(bob)
alice.say("大家好")
# [Bob] 收到 Alice 的消息: 大家好
bob.say("你好 Alice")
# [Alice] 收到 Bob 的消息: 你好 Alice
要点:User 不引用另一个 User,只通过 _mediator.send(self, message) 发消息;谁收到、怎么转发全由 ChatRoom 决定,要改成“只发给某人”或“带权限”只需改 ChatRoom。
四、示例二:表单与控件(中介者协调多个输入框、按钮)
需求:表单有:用户名输入框、密码输入框、提交按钮。逻辑:只有两个框都非空时按钮才可用;点提交时由中介者收集两个框的值并统一处理。控件之间不直接引用,只和“表单中介者”通信。
4.1 中介者接口 + 具体中介者(表单)
class FormMediator(ABC):
@abstractmethod
def notify(self, sender, event: str):
"""控件发生事件时调用,如 'changed' / 'clicked' """
pass
class LoginForm(FormMediator):
def __init__(self):
self.username = None # 由中介者设置
self.password = None
self.submit_btn = None
def set_components(self, username, password, submit_btn):
self.username = username
self.password = password
self.submit_btn = submit_btn
def notify(self, sender, event: str):
if event == "changed":
self._update_submit_state()
elif event == "clicked" and sender == self.submit_btn:
self._do_submit()
def _update_submit_state(self):
ok = (self.username and self.username.value) and (self.password and self.password.value)
if self.submit_btn:
self.submit_btn.set_enabled(ok)
def _do_submit(self):
u = self.username.value if self.username else ""
p = self.password.value if self.password else ""
print(f" 提交: 用户名={u}, 密码={p}")
4.2 同事:输入框、按钮(只依赖中介者)
class InputBox:
def __init__(self, mediator: FormMediator):
self._mediator = mediator
self._value = ""
@property
def value(self):
return self._value
def set_value(self, v: str):
self._value = v
self._mediator.notify(self, "changed")
class Button:
def __init__(self, mediator: FormMediator):
self._mediator = mediator
self._enabled = False
def set_enabled(self, enabled: bool):
self._enabled = enabled
def click(self):
if self._enabled:
self._mediator.notify(self, "clicked")
4.3 使用
form = LoginForm()
username = InputBox(form)
password = InputBox(form)
submit_btn = Button(form)
form.set_components(username, password, submit_btn)
username.set_value("admin")
password.set_value("123")
submit_btn.click() # 提交: 用户名=admin, 密码=123
要点:InputBox、Button 不互相引用;内容变化或点击时只调 mediator.notify(self, event),由表单中介者决定“更新按钮状态”和“执行提交”。
五、示例三:订单与库存、支付(简化版)
需求:下单要协调“订单”“库存”“支付”:订单创建后通知中介者,中介者依次调库存扣减、支付扣款;若某步失败,由中介者决定回滚或通知。各模块不直接调对方,只通过中介者。
class OrderMediator:
def __init__(self):
self.order = None
self.inventory = None
self.payment = None
def set_components(self, order, inventory, payment):
self.order = order
self.inventory = inventory
self.payment = payment
def place_order(self, user_id: str, product_id: str, qty: int, amount: float) -> bool:
"""协调:创建订单 -> 扣库存 -> 扣款"""
if not self.inventory.reserve(product_id, qty):
return False
if not self.payment.charge(user_id, amount):
self.inventory.cancel_reserve(product_id, qty)
return False
self.order.create(user_id, product_id, qty, amount)
return True
class OrderModule:
def __init__(self, mediator: OrderMediator):
self._mediator = mediator
def create(self, user_id, product_id, qty, amount):
print(" [订单] 创建成功")
class InventoryModule:
def __init__(self, mediator: OrderMediator):
self._mediator = mediator
def reserve(self, product_id, qty): return True
def cancel_reserve(self, product_id, qty): pass
class PaymentModule:
def __init__(self, mediator: OrderMediator):
self._mediator = mediator
def charge(self, user_id, amount): return True
# 使用
mediator = OrderMediator()
order = OrderModule(mediator)
inv = InventoryModule(mediator)
pay = PaymentModule(mediator)
mediator.set_components(order, inv, pay)
mediator.place_order("u1", "p1", 2, 100.0)
要点:Order、Inventory、Payment 不直接互相调;下单流程(顺序、回滚)全在 OrderMediator 里,便于改规则。
六、中介者 vs 其他模式
| 模式 | 目的 | 与中介者的区别 |
|---|---|---|
| 中介者 | 多对象交互集中到中介者,对象只与中介者通信 | 强调“多对多”协调、对象间不直接引用。 |
| 观察者 | 一对多通知,主题通知多个订阅者 | 观察者里主题不知道订阅者具体是谁(解耦),但通常是“一个主题多订阅者”;中介者是“多对象通过一个中介互相协作”。 |
| 外观 | 为子系统提供简单入口 | 外观是“客户 -> 外观 -> 子系统”的简化调用;中介者是“多个同事 中介者”的互相协作。 |
简单记忆:中介者 = 多对象都连到中介者 + 交互逻辑在中介者里 + 对象之间不直接引用。
七、常见问题与注意点
7.1 中介者会不会变成“上帝类”?
若所有交互都塞进一个中介者,中介者会很大。可以按“子场景”拆成多个中介者(如聊天室一个、表单一个),或让中介者只做“转发/路由”,具体逻辑由各同事或子协调器完成。
7.2 同事一定要持有中介者引用吗?
通常是的:同事要发起协作时,需要调中介者的方法;中介者也要能访问到同事(持有列表或注册表),才能转发。有时用“事件总线”代替单一中介者,同事只发事件、订阅事件,本质仍是“通过中间层协作”。
7.3 何时用中介者、何时用观察者?
若主要是“一个对象状态变化,通知多个其他对象”,用观察者更自然;若是“多个对象两两或多人要协作、对话、按流程配合”,用中介者更合适。
八、小结
- 中介者模式:引入中介者对象,多对象之间的交互都通过中介者进行;同事对象不直接引用彼此,只依赖中介者,从而降低耦合、集中交互逻辑。
- 两个角色:中介者(定义通信接口、持有/识别同事、负责转发与协调)、同事(持有中介者引用,通过中介者与其他对象协作)。
- 典型用途:聊天室、表单多控件协调、订单-库存-支付等需要多模块协作的场景。
- 实现要点:同事只依赖中介者;中介者知道所有参与协作的对象,在收到请求时决定通知谁、按什么规则处理。
建议先写“聊天室”一例,再试“表单控件”或“订单协调”,体会“对象只和中介者说话、交互规则集中在一处”,这样对中介者模式会掌握得比较扎实。