中介者模式练习

中介者模式练习——聊天室与表单协调

按《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);启用逻辑、提交逻辑都在中介者里。

自检问题

  1. 同事对象为什么要持有中介者引用?
    因为要发起协作时,必须通过中介者把消息/事件传出去,而不能直接找其他同事;所以每个同事需要能调用 mediator 的方法。

  2. 若要让“只有好友才能收到消息”,应该改哪里?
    中介者(如 ChatRoom)的 send 逻辑:根据 sender 和“好友关系”决定通知哪些 user,同事类(User)不用改。

  3. 中介者模式和“对象直接互相调用”相比,优点是什么?
    对象之间不直接引用,耦合低;交互规则集中在中介者,改规则(谁通知谁、何时启用按钮等)只改一处;增加新同事时,只需在中介者里注册,不必改现有同事。

做完以上两道练习,再对照《中介者模式》文档中的示例,对中介者模式会掌握得比较扎实。建议每道题先自己写一遍,再对照参考答案和验证要点检查。

发表评论