Skip to content

中介者模式(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 B

2.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);
        }
    }
}

七、全文总结

中介者模式的核心是 将对象间交互集中到中介者,解耦网状依赖

核心要点

  1. 同事对象通过中介者通信,互不直接引用
  2. 将网状依赖转为星型依赖
  3. 适用于 GUI 协调、聊天系统、事件总线等场景
  4. 避免中介者变成上帝类,按功能拆分
  5. 可与观察者模式、事件总线结合使用