中介者模式(Mediator)
前言
中介者模式将对象间的交互集中到中介者对象中,使各对象之间不需要显式相互引用。它是解耦多对象交互、减少网状依赖的经典模式。本文将详细讲解中介者模式的核心原理及实际应用。
一、核心概念
1.1 定义
用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
1.2 核心角色
| 角色 | 说明 |
|---|---|
| 中介者接口(Mediator) | 定义同事对象通信接口 |
| 具体中介者(ConcreteMediator) | 实现协调逻辑 |
| 同事类(Colleague) | 通过中介者与其他同事通信 |
| 具体同事(ConcreteColleague) | 实现具体行为 |
1.3 解决的问题
无中介者:网状依赖
A ─── B
│ ╲ ╱ │
│ ╳ │
│ ╱ ╲ │
C ─── D
→ N 个对象需要 N×(N-1)/2 个连接
有中介者:星型依赖
A B
╲ ╱
M
╱ ╲
C D
→ 每个对象只与中介者通信二、代码实现
2.1 基础实现
php
<?php
// 中介者接口
interface Mediator
{
public function notify(Colleague $sender, string $event): void;
}
// 同事基类
abstract class Colleague
{
protected Mediator $mediator;
public function __construct(Mediator $mediator)
{
$this->mediator = $mediator;
}
}
// 具体同事 A
class ConcreteColleagueA extends Colleague
{
public function doA(): void
{
echo "Colleague A: doing A" . PHP_EOL;
$this->mediator->notify($this, "A");
}
public function reactB(): void
{
echo "Colleague A: reacting to B" . PHP_EOL;
}
}
// 具体同事 B
class ConcreteColleagueB extends Colleague
{
public function doB(): void
{
echo "Colleague B: doing B" . PHP_EOL;
$this->mediator->notify($this, "B");
}
public function reactA(): void
{
echo "Colleague B: reacting to A" . PHP_EOL;
}
}
// 具体中介者
class ConcreteMediator implements Mediator
{
private ConcreteColleagueA $colleagueA;
private ConcreteColleagueB $colleagueB;
public function setColleagueA(ConcreteColleagueA $a): void
{
$this->colleagueA = $a;
}
public function setColleagueB(ConcreteColleagueB $b): void
{
$this->colleagueB = $b;
}
public function notify(Colleague $sender, string $event): void
{
if ($event === "A") {
$this->colleagueB->reactA();
} elseif ($event === "B") {
$this->colleagueA->reactB();
}
}
}
// 使用
$mediator = new ConcreteMediator();
$a = new ConcreteColleagueA($mediator);
$b = new ConcreteColleagueB($mediator);
$mediator->setColleagueA($a);
$mediator->setColleagueB($b);
$a->doA();
// Colleague A: doing A
// Colleague B: reacting to A
$b->doB();
// Colleague B: doing B
// Colleague A: reacting to B2.2 实际案例:聊天室
php
<?php
// 中介者:聊天室
class ChatRoom
{
/** @var array<string, User> */
private array $users = [];
public function register(User $user): void
{
$this->users[$user->name] = $user;
$user->setChatRoom($this);
}
public function send(string $from, string $to, string $message): void
{
$target = $this->users[$to] ?? null;
if ($target !== null) {
$target->receive($from, $message);
} else {
echo "User {$to} not found" . PHP_EOL;
}
}
public function broadcast(string $from, string $message): void
{
foreach ($this->users as $name => $user) {
if ($name !== $from) {
$user->receive($from, $message);
}
}
}
}
// 同事:用户
class User
{
private ?ChatRoom $chatRoom = null;
public function __construct(public string $name) {}
public function setChatRoom(ChatRoom $chatRoom): void
{
$this->chatRoom = $chatRoom;
}
public function send(string $to, string $message): void
{
echo "{$this->name} → {$to}: {$message}" . PHP_EOL;
$this->chatRoom?->send($this->name, $to, $message);
}
public function receive(string $from, string $message): void
{
echo "{$this->name} received from {$from}: {$message}" . PHP_EOL;
}
public function broadcast(string $message): void
{
echo "{$this->name} broadcasts: {$message}" . PHP_EOL;
$this->chatRoom?->broadcast($this->name, $message);
}
}
// 使用
$room = new ChatRoom();
$alice = new User("Alice");
$bob = new User("Bob");
$charlie = new User("Charlie");
$room->register($alice);
$room->register($bob);
$room->register($charlie);
$alice->send("Bob", "Hi Bob!");
$bob->send("Alice", "Hello Alice!");
$alice->broadcast("Good morning everyone!");2.3 实际案例:UI 组件协调
php
<?php
// 中介者:表单协调器
class FormMediator
{
private TextInput $usernameInput;
private TextInput $passwordInput;
private Button $submitButton;
private Label $errorLabel;
public function __construct(
TextInput $usernameInput,
TextInput $passwordInput,
Button $submitButton,
Label $errorLabel
) {
$this->usernameInput = $usernameInput;
$this->passwordInput = $passwordInput;
$this->submitButton = $submitButton;
$this->errorLabel = $errorLabel;
// 绑定事件
$this->usernameInput->onChange(fn() => $this->validate());
$this->passwordInput->onChange(fn() => $this->validate());
$this->submitButton->onClick(fn() => $this->submit());
}
private function validate(): void
{
$username = $this->usernameInput->getValue();
$password = $this->passwordInput->getValue();
if (strlen($username) < 3) {
$this->errorLabel->setText("Username too short");
$this->submitButton->setEnabled(false);
} elseif (strlen($password) < 6) {
$this->errorLabel->setText("Password too short");
$this->submitButton->setEnabled(false);
} else {
$this->errorLabel->setText("");
$this->submitButton->setEnabled(true);
}
}
private function submit(): void
{
$username = $this->usernameInput->getValue();
$password = $this->passwordInput->getValue();
echo "Submitting: {$username}" . PHP_EOL;
// ...API 调用
}
}
// UI 组件
class TextInput
{
private string $value = "";
/** @var callable[] */
private array $listeners = [];
public function getValue(): string
{
return $this->value;
}
public function setValue(string $value): void
{
$this->value = $value;
foreach ($this->listeners as $listener) {
$listener();
}
}
public function onChange(callable $listener): void
{
$this->listeners[] = $listener;
}
}
class Button
{
private bool $enabled = false;
/** @var callable[] */
private array $listeners = [];
public function setEnabled(bool $enabled): void
{
$this->enabled = $enabled;
}
public function isEnabled(): bool
{
return $this->enabled;
}
public function onClick(callable $listener): void
{
$this->listeners[] = $listener;
}
public function click(): void
{
if ($this->enabled) {
foreach ($this->listeners as $listener) {
$listener();
}
}
}
}
class Label
{
private string $text = "";
public function setText(string $text): void
{
$this->text = $text;
}
public function getText(): string
{
return $this->text;
}
}
// 使用
$form = new FormMediator(
new TextInput(),
new TextInput(),
new Button(),
new Label()
);三、适用场景
| 场景 | 说明 |
|---|---|
| GUI 协调 | 组件间联动 |
| 聊天系统 | 用户间消息路由 |
| 机场调度 | 飞机与塔台通信 |
| 事件总线 | 全局事件分发 |
| MVC 架构 | Controller 作为中介 |
| 智能家居 | 设备间联动 |
四、优缺点分析
| 优点 | 缺点 |
|---|---|
| 解耦同事对象 | 中介者可能变上帝类 |
| 集中控制交互 | 单点故障 |
| 易于扩展新同事 | 中介者复杂度增加 |
| 替代网状依赖 | 同事间通信需经中介者 |
| 符合迪米特法则 | 性能可能成为瓶颈 |
五、常见踩坑与问题排查
5.1 中介者变成上帝类
php
<?php
// 问题:中介者承担过多职责
class GodMediator
{
public function handleUser() { /* ... */ }
public function handleOrder() { /* ... */ }
public function handlePayment() { /* ... */ }
public function handleShipping() { /* ... */ }
public function handleNotification() { /* ... */ }
}
// 解决:按功能拆分多个中介者
class UserMediator { /* 用户相关 */ }
class OrderMediator { /* 订单相关 */ }5.2 同事直接通信
php
<?php
// 问题:同事绕过中介者直接通信
class BadColleague
{
public function __construct(private Colleague $other) {} // 直接引用其他同事
public function doSomething(): void
{
$this->other->react(); // 直接调用
}
}
// 解决:所有通信必须经中介者
class GoodColleague extends Colleague
{
public function doSomething(): void
{
$this->mediator->notify($this, "event");
}
}六、优化方案与进阶
6.1 事件总线
php
<?php
// 通用事件总线
class EventBus
{
/** @var array<string, callable[]> */
private array $listeners = [];
public function on(string $event, callable $listener): void
{
if (!isset($this->listeners[$event])) {
$this->listeners[$event] = [];
}
$this->listeners[$event][] = $listener;
}
public function emit(string $event, $data = null): void
{
foreach ($this->listeners[$event] ?? [] as $listener) {
$listener($data);
}
}
public function off(string $event, callable $listener): void
{
if (!isset($this->listeners[$event])) return;
$index = array_search($listener, $this->listeners[$event], true);
if ($index !== false) {
array_splice($this->listeners[$event], $index, 1);
}
}
}
// 使用:松耦合通信
$bus = new EventBus();
$user1 = new class {
public string $name = "Alice";
public function init(EventBus $bus): void
{
$bus->on("message", function($data) {
echo "{$this->name} received: {$data}" . PHP_EOL;
});
}
};
$user1->init($bus);
$bus->emit("message", "Hello");6.2 与观察者模式结合
php
<?php
// 观察者接口
interface Observer
{
public function update($data): void;
}
// 中介者内部使用观察者模式
class MediatorWithObserver
{
/** @var array<string, Observer[]> */
private array $observers = [];
public function subscribe(string $event, Observer $observer): void
{
if (!isset($this->observers[$event])) {
$this->observers[$event] = [];
}
$this->observers[$event][] = $observer;
}
public function publish(string $event, $data = null): void
{
foreach ($this->observers[$event] ?? [] as $observer) {
$observer->update($data);
}
}
}七、全文总结
中介者模式的核心是 将对象间交互集中到中介者,解耦网状依赖。
核心要点:
- 同事对象通过中介者通信,互不直接引用
- 将网状依赖转为星型依赖
- 适用于 GUI 协调、聊天系统、事件总线等场景
- 避免中介者变成上帝类,按功能拆分
- 可与观察者模式、事件总线结合使用
