设计模式之中介模式

通俗地讲,**中介者模式(Mediator Pattern)**就是给一群乱成一团、互相指手画脚的对象找了一个"大管家"或者"协调员"。

如果没有这个管家,大家(对象)之间需要互相打招呼,关系网会像乱麻一样;有了管家,大家只需要跟管家说话,剩下的琐事由管家去转达。

想象一下一个商场有:空调、照明灯、烟雾报警器、自动喷淋头


场景对比:乱套 vs 有序

❌ 如果没有中介者(网状关系)
  • 烟雾报警器 发现火情,它得自己去给空调 发指令(关闭送风),去给照明灯 发指令(切换应急灯),还要去给喷淋头发指令(喷水)。

  • 后果: 报警器必须知道空调、灯、喷淋头的所有接口。如果商场新加了一个"自动报警电话",你还得改报警器的代码。这叫高度耦合

✅ 如果有中介者(星形关系)
  • 所有设备只连接到**"物业管理系统"**。

  • 报警器喊一声:"着火了!"。

  • **物业管理系统(中介者)**收到后,按照逻辑去关闭空调、打开喷淋、切换灯光。

  • 后果: 报警器只管报警,至于谁去救火、谁去关风,它一概不操心。

老规矩,用代码解释两者区别:

1.无中介者模式

复制代码
#include <iostream>

// 窗帘
class Curtain {
public:
    void open() { std::cout << "窗帘打开了。\n"; }
};

// 咖啡机
class CoffeeMachine {
public:
    void start() { std::cout << "咖啡机开始煮咖啡。\n"; }
};

// 闹钟:它是整个逻辑的中心,必须持有其他两个对象的指针
class Alarm {
private:
    Curtain* curtain;
    CoffeeMachine* coffee;
public:
    // 构造函数:你得亲手把窗帘和咖啡机塞给闹钟
    Alarm(Curtain* c, CoffeeMachine* m) : curtain(c), coffee(m) {}

    void ring() {
        std::cout << "闹钟响了!\n";
        curtain->open(); // 闹钟直接指挥窗帘
        coffee->start(); // 闹钟直接指挥咖啡机
    }
};

int main() {
    Curtain* c = new Curtain();
    CoffeeMachine* m = new CoffeeMachine();
    
    // 这种初始化方式叫"强耦合":没有c和m,alarm连实例都创不出来
    Alarm* alarm = new Alarm(c, m);
    
    alarm->ring();

    delete c; delete m; delete alarm;
    return 0;
}

2.有中介者模式

复制代码
#include <iostream>

class Mediator; // 前向声明

// 抽象设备(所有设备只和中介者打交道)
class Device {
protected:
    Mediator* mediator;
public:
    Device(Mediator* m) : mediator(m) {}
};

// 抽象中介者接口
class Mediator {
public:
    virtual void changed(Device* sender) = 0;
};

// --- 具体设备 ---

class Alarm : public Device {
public:
    using Device::Device;
    void ring() {
        std::cout << "闹钟响了!\n";
        mediator->changed(this); // 闹钟只负责报告:我响了,至于谁跟着动,它不操心
    }
};

class Curtain : public Device {
public:
    using Device::Device;
    void open() { std::cout << "窗帘打开了。\n"; }
};

class CoffeeMachine : public Device {
public:
    using Device::Device;
    void start() { std::cout << "咖啡机开始煮咖啡。\n"; }
};

// --- 具体中介者:智能大脑 ---

class SmartHomeMediator : public Mediator {
public:
    Alarm* alarm;
    Curtain* curtain;
    CoffeeMachine* coffee;

    // 中介者接收信号,决定如何联动
    void changed(Device* sender) override {
        if (sender == alarm) {
            curtain->open();
            coffee->start();
        }
    }
};

int main() {
    // 1. 先把大脑(中介者)创造出来
    SmartHomeMediator* mediator = new SmartHomeMediator();

    // 2. 创建设备时,只需要告诉它们大脑是谁就行
    Alarm* alarm = new Alarm(mediator);
    Curtain* curtain = new Curtain(mediator);
    CoffeeMachine* coffee = new CoffeeMachine(mediator);

    // 3. 大脑需要知道它管哪些设备
    mediator->alarm = alarm;
    mediator->curtain = curtain;
    mediator->coffee = coffee;

    // 4. 触发动作
    alarm->ring();

    delete mediator; delete alarm; delete curtain; delete coffee;
    return 0;
}

3. 这两个代码最核心的区别

如果你看 main 函数,可能觉得中介者模式更麻烦了。但请看这两个类的成员变量

  • 无中介者的 Alarm 里面存了 Curtain*CoffeeMachine*

    这意味着: Alarm 的代码里必须 #include "Curtain.h"#include "CoffeeMachine.h"。它被这两个类紧紧套牢了。

  • 有中介者的 Alarm 里面只有一个 Mediator*

    这意味着: Alarm 不需要知道窗帘是什么,也不需要知道咖啡机是什么。它只知道"当我有事时,找中介者汇报"。

中介者模式的精髓不是为了减少代码量,而是为了"划清界限"。

  • 没有中介者:Alarm主控器(又当闹钟又当管家)。

  • 有了中介者:Alarm 只是一个纯粹的闹钟

4.场景:闹钟响了 --》窗帘开、咖啡机动、新增:扫地机扫地

<1> 无中介者模式:牵一发而动全身

在没有中介者时,Alarm 类就像一个"管家婆",它必须认识所有人。增加扫地机,你得去拆开 Alarm 类的代码进行大手术。

复制代码
// --- 1. 新增一个扫地机类 ---
class Robot {
public:
    void clean() { std::cout << "【扫地机】开始清扫地板...\n"; }
};

// --- 2. 必须修改原有的 Alarm 类(大手术!) ---
class Alarm {
    Curtain* pCurtain;
    CoffeeMachine* pCM;
    Robot* pRobot; // 【修改点1】:增加新成员
public:
    // 【修改点2】:修改构造函数,强迫闹钟必须认识机器人
    Alarm(Curtain* c, CoffeeMachine* m, Robot* r) : pCurtain(c), pCM(m), pRobot(r) {}
    
    void ring() {
        std::cout << "闹钟响了!\n";
        pCurtain->open();
        pCM->start();
        pRobot->clean(); // 【修改点3】:修改业务逻辑
    }
};

// --- 3. Main 函数也得改 ---
int main() {
    Curtain* c = new Curtain();
    CoffeeMachine* m = new CoffeeMachine();
    Robot* r = new Robot(); // 新增对象
    
    Alarm* alarm = new Alarm(c, m, r); // 这里的传参变了
    alarm->ring();
    return 0;
}

闹钟类非常"脆弱"。每次家里买新家电,你都得把闹钟拆开接几根线,这在大型工程里叫**"代码污染"**。


<2> 中介者模式:优雅的"插拔式"扩展

在有中介者时,Alarm一行代码都不用改。它只负责喊一声"我响了",剩下的交给"大脑"去协调。

复制代码
// --- 1. 新增扫地机类(继承自Device) ---
class Robot : public Device {
public:
    using Device::Device;
    void clean() { std::cout << "【扫地机】开始清扫地板...\n"; }
};

// --- 2. 修改中介者(大脑)的逻辑即可 ---
class SmartHomeMediator : public Mediator {
    Alarm* alarm;
    Curtain* curtain;
    CoffeeMachine* coffee;
    Robot* robot; // 新增机器人引用
public:
    void setDevices(Alarm* a, Curtain* c, CoffeeMachine* cm, Robot* r) {
        alarm = a; curtain = c; coffee = cm; robot = r;
    }

    void changed(Device* d) override {
        if (d == alarm) { // 闹钟响了,大脑下令:
            curtain->open();
            coffee->start();
            robot->clean(); // 【唯一修改点】:在这里加一行逻辑
        }
    }
};

// --- 3. Main 函数逻辑清爽 ---
int main() {
    SmartHomeMediator* mediator = new SmartHomeMediator();

    // 所有设备初始化方式一模一样,只认中介者
    Alarm* alarm = new Alarm(mediator);
    Curtain* curtain = new Curtain(mediator);
    CoffeeMachine* coffee = new CoffeeMachine(mediator);
    Robot* robot = new Robot(mediator); // 新设备轻松接入

    mediator->setDevices(alarm, curtain, coffee, robot);

    // 闹钟触发,闹钟的代码完全没动过!
    alarm->ring(); 

    return 0;
}

关键区别在哪?

比较项 无中介者(硬编码) 中介者模式
闹钟类的代码 必须修改(增加指针、改构造、改逻辑)。 完全不动(它甚至不知道机器人的存在)。
类的关系 闹钟关联了:窗帘、咖啡机、机器人。 闹钟只关联了:中介者。
谁更累? 闹钟很累,它得管所有人的死活。 中介者很累,但它集中处理了所有麻烦。
  • 不用中介者: 你的代码像毛线团,拽一根线,整团都要跟着动。

  • 用中介者: 你的代码像插线板。想加个电器?插上去,然后在插线板的控制开关(中介者)里写好逻辑就行了。

这就叫"解耦"。下次如果你发现一个类里的指针越来越多,多到你数不过来的时候,就是该请"中介者"出场的时候了!

5.疑问

中介者模式并没有让代码总量变少,甚至"中介者"类自己改得比谁都多???

我们可以从以下三个维度看清"改中介者"和"改闹钟"的本质区别:


(1) "稳定的心"与"善变的大脑"

在软件开发中,我们希望**底层的执行类(闹钟、灯、机器人)**越纯粹越好,不要因为业务逻辑变了就去动它们。

  • 如果不请中介者:

    闹钟本来的职责是"计时"和"发声"。但为了联动,你给它塞了机器人、咖啡机。这时候,闹钟就不再是一个纯粹的闹钟了,它变成了一个"家电指挥部"。

    后果: 如果你要把这个闹钟代码拿到另一个没有机器人的项目里用,代码会报错,因为它里面写死了机器人。

  • 如果请了中介者:

    闹钟只管"响",它不认识任何人。

    优点: 闹钟类达到了**"100%可复用"**。它可以在任何地方工作,因为它只依赖一个抽象的中介者接口。


(2) "改一处"与"改一堆"的风险对比

你觉得中介者改了很多,是因为在这个小例子里只有 3 个设备。想象一下如果你有 20 个设备,且它们之间有复杂的互相影响(比如:开 A 必须关 B,开 C 必须检查 D 是否关闭):

  • 不用中介者:

    你可能需要修改 10 个类的代码,每个类都要加逻辑、加成员变量。改的地方越多,写出 BUG 的概率是指数级上升的。(你可能会忘了改其中一个,导致系统崩溃)。

  • 用中介者:

    所有的逻辑乱如麻,但乱得整齐------全都在中介者一个类里。

    好处: 你只要盯着中介者这一个文件改就行了。测试的时候,你也只需要重点测试中介者逻辑。


(3) 程序员的"心智负担"

  • 乱战模式(无中介者): 当你想看"扫地机"什么时候动,你得去翻闹钟的代码、翻传感器的代码、翻手机 APP 的代码......因为谁都可能指挥它。

  • 上帝模式(中介者): 当你想看"扫地机"什么时候动,你只需要打开 SmartHomeMediator.cpp。这个文件就是整个家的**"逻辑说明书"**。


(4) 数据对比

修改维度 修改 Alarm 类 (无中介者) 修改 Mediator 类 (有中介者)
影响范围 (Alarm 类本身被破坏,无法独立使用) (Alarm 类完全没变,依然纯洁)
逻辑清晰度 (逻辑散落在各个角落) (逻辑集中在"大脑"里)
编译成本 (所有引用 Alarm 的地方可能都要重新编译) (通常只需重新编译中介者)
相关推荐
触底反弹2 小时前
🤯 面试被问 AI Workflow 和 Agent 有啥区别?3 张图 + 2 段代码讲清楚!
人工智能·设计模式·面试
杨充1 天前
10.可测试性实战设计
设计模式·开源·代码规范
杨充1 天前
9.重构十二式的实战
设计模式·开源·代码规范
杨充1 天前
6.设计原则的全景图
设计模式·开源·全栈
杨充1 天前
2.面向对象的特性
设计模式
杨充1 天前
7.SOLID原则案例汇
设计模式·开源·全栈
杨充1 天前
8.反模式与坏味道
设计模式·开源·代码规范
杨充1 天前
3.接口vs抽象类比较
设计模式
咖啡八杯1 天前
文法、BNF与AST
java·设计模式·解释器模式·ast·文法