中介者模式练习——聊天室与表单协调
按《Python 设计模式——中介者模式》的建议,通过两道练习巩固:① 聊天室(用户只通过中介者发消息);② 表单控件协调(输入框与按钮通过中介者联动)。每步都有完整可运行代码和验证要点。
练习一:聊天室(用户通过中介者发消息)
目的
体会同事对象不直接引用彼此,只和中介者通信;发消息时调“中介者.send(我, 内容)”,由中介者决定把消息转给谁(如广播给除自己外的所有人)。
要求
- 实现 ChatRoom(中介者):能添加用户,提供 send(sender, message);send 被调用时,把 message 广播给除 sender 以外的所有已添加用户(即对每个其他用户调用其 receive(sender, message))。
- 实现 User(同事):有 name,构造时传入 Mediator;提供 say(message),内部调 mediator.send(self, message);提供 receive(sender, message),被中介者调用时打印一条“[自己的名字] 收到 [发送者名字] 的消息: 内容”。
- 客户代码:创建一个 ChatRoom,添加 3 个 User,让其中两个用户各 say 一句,验证另外两个用户都收到对应消息,且发送者自己不会收到自己发的消息。
参考答案
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)
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}")
# 使用
room = ChatRoom()
alice = User("Alice", room)
bob = User("Bob", room)
carol = User("Carol", room)
room.add_user(alice)
room.add_user(bob)
room.add_user(carol)
print("Alice 发言:")
alice.say("大家好")
print("Bob 发言:")
bob.say("你好")
验证要点
- Alice 说“大家好”时,Bob 和 Carol 都打印“收到 Alice 的消息: 大家好”,Alice 自己不打印(因为 send 里跳过了 sender)。
- Bob 说“你好”时,Alice 和 Carol 都打印“收到 Bob 的消息: 你好”,Bob 自己不打印。
- 确认:User 之间没有直接引用,只通过
_mediator.send(self, message)发消息;谁收到、怎么转发全由 ChatRoom 决定。
练习二:表单控件协调(输入框与按钮通过中介者联动)
目的
多个控件(如两个输入框、一个按钮)不直接互相引用;输入框内容变化或按钮被点击时,只通知中介者(表单);由中介者决定“何时启用按钮”“提交时收集哪些数据”,体会交互逻辑集中在中介者。
要求
- 实现 FormMediator(中介者):能设置 3 个组件——用户名输入框、密码输入框、提交按钮;提供 notify(sender, event),其中
- event == "changed" 时:若两个输入框都非空则启用按钮,否则禁用按钮;
- event == "clicked" 且 sender 是提交按钮时:打印“提交: 用户名=xxx, 密码=xxx”(从两个输入框取值)。
- 实现 InputBox(同事):有 value(字符串),有 set_value(v)(设置后调 mediator.notify(self, "changed"));构造时传入 FormMediator。
- 实现 Button(同事):有 enabled 状态,有 set_enabled(True/False),有 click()(若 enabled 则调 mediator.notify(self, "clicked"));构造时传入 FormMediator。
- 客户代码:创建表单、两个输入框、一个按钮并 set_components;先只填用户名不填密码,点提交应无输出(或按钮不可点);再填密码后点提交,应打印提交内容。
参考答案
from abc import ABC, abstractmethod
class FormMediator(ABC):
@abstractmethod
def notify(self, sender, event: str):
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 is self.submit_btn:
self._do_submit()
def _update_submit_state(self):
u_ok = self.username and len((self.username.value or "").strip()) > 0
p_ok = self.password and len((self.password.value or "").strip()) > 0
if self.submit_btn:
self.submit_btn.set_enabled(u_ok and p_ok)
def _do_submit(self):
u = (self.username.value or "").strip()
p = (self.password.value or "").strip()
print(f" 提交: 用户名={u}, 密码={p}")
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 or ""
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")
# 使用
form = LoginForm()
username = InputBox(form)
password = InputBox(form)
submit_btn = Button(form)
form.set_components(username, password, submit_btn)
username.set_value("admin")
# 此时只填了用户名,按钮应被禁用,点提交无效果
submit_btn.click() # 无输出(按钮未 enabled)
password.set_value("123456")
submit_btn.click() # 提交: 用户名=admin, 密码=123456
验证要点
- 只填用户名时,按钮应为禁用(或 click 不触发提交);填完密码后,按钮变为可用,点提交打印 提交: 用户名=admin, 密码=123456。
- 确认:InputBox 和 Button 之间没有直接引用;内容变化和点击都只调 mediator.notify(self, event);“何时启用按钮”“提交时收集什么”全在 LoginForm 里。
两步汇总与自检
| 练习 | 重点 | 关键点 |
|---|---|---|
| 一 | 聊天室 | 用户只持有 Mediator;发消息走 mediator.send(self, msg);谁收到由 ChatRoom 决定(如广播且排除自己)。 |
| 二 | 表单协调 | 控件只持有 FormMediator;变化/点击时 notify(mediator, event);启用逻辑、提交逻辑都在中介者里。 |
自检问题
-
同事对象为什么要持有中介者引用?
因为要发起协作时,必须通过中介者把消息/事件传出去,而不能直接找其他同事;所以每个同事需要能调用 mediator 的方法。 -
若要让“只有好友才能收到消息”,应该改哪里?
改中介者(如 ChatRoom)的 send 逻辑:根据 sender 和“好友关系”决定通知哪些 user,同事类(User)不用改。 -
中介者模式和“对象直接互相调用”相比,优点是什么?
对象之间不直接引用,耦合低;交互规则集中在中介者,改规则(谁通知谁、何时启用按钮等)只改一处;增加新同事时,只需在中介者里注册,不必改现有同事。
做完以上两道练习,再对照《中介者模式》文档中的示例,对中介者模式会掌握得比较扎实。建议每道题先自己写一遍,再对照参考答案和验证要点检查。