中介者模式

房东:到处找租客可太麻烦, 直接交给中介, 交给他们管理吧~

意图

用一个中介对象来封装一系列的对象交互.中介者使各对象不需要显式地相互引用, 从而使其耦合松散, 而且可以独立地改变它们之间的交互.

动机

GoF 中提到的一点, 我觉得很有道理, 面向对象设计鼓励将行为分布到各个对象中.这种分布可能会导致对象间有许多连接.在最坏的情况下, 每一个对象都知道其他所有对象.

example

我点击数字按键 Button, LineEdit 上增加对应的数字, 点击删除按键, LineEdit 就会删除最后一位, LineEdit 中为空的时候, 某个按钮不能使用等等

我们不可能让 LineEdit 这个类去监听所有按键的点击事件, 统筹所有的交互行为, 一方面这只是计算器界面中组件间依赖关系, 是一种特定的依赖关系, 另一方面 LineEidt 这个类其实不关心组件中这么复杂的依赖关系, 如果需要交由 LineEdit 统筹, 那就需要在各种各样的场景中定制 LineEidt 控件, 但是并不仅仅只有 LineEidt 会和其他组件会有依赖关系, 这样的设计只会让代码显得越来越乱

所以就需要引入一个单独的中介者 (mediator) 对象以避免这个问题, 中介者负责控制和协调一组对象间的交互, 中介者充当一个中介以使组中的对象不再相互显式引用. 这些对象仅知道中介者, 从而减少了相互连接的数目.

UML 图

uml

  • Mediator (中介者)
    • 中介者定义一个接口用于与各个同事 (Colleague) 对象通信
  • ConcreteMediator (具体中介者)
    • 具体中介者通过协调各个同事对象实现协作行为
    • 了解并维护它的各个同事
  • Colleague (同事类)
    • 每一个同事类都知道它的中介者对象
    • 每一个同事对象在需要与其他的同事通信的时候, 以它的中介者通信

从 UML 图中可以看出以下几点

  • ConcreteMediator 等于是持有所有需要交互的对象, 由它来负责所有交互, 很明显的优点是 Colleague 对象变得简洁了, Colleague 间的耦合松散了, 但是中介者就变得特别复杂了, 等于是将原本 Colleague 间交互的复杂性转嫁给了中介者, 这可能导致最后中介者本身变成一个难以维护的庞然大物
  • 抽象的 Mediator 和抽象的 Colleague, 在这里我觉得更多是定义一种通信的接口, Mediator 中定义一个特殊的通知接口, 各 Colleague 在通信时调用该接口, 这是一种很简单的思路
    此外也可以使用观察者模式去实现这种通信, Mediator 是一个观察者, Colleague 是一个 Subject, 一旦其状态改变的就发送通知给 Mediator, 通知可以直接将需要传递的信息也携带 (推模式), 也可以仅仅是通知然后让 Mediator 自己来取 (拉模式), 所以这种结构一开始让我感觉特别相似 观察者模式, 连 UML 图都很像

应用场景

  • 一组对象以定义良好但是复杂的方式进行通信. 产生的相互依赖关系结构混乱且难以理解.
  • 一个对象引用其他很多对象并且直接与这些对象通信, 导致难以复用该对象.
  • 想定制一个分布在多个类中的行为, 而又不想生成太多的子类.

举个例子

直接使用前面动机中的例子, 但是我们简化一下

我们只有2个数字按键 (Button), 分别是 12, 以及一个显示点击数字结果的 LineEdit, 每次点击按钮之后将按钮的值加到显示控件最后一位

首先定义中介者的基类 Mediator, 它需要定义一个收到按键触发的函数 buttonChanged()

class Widget;
class Mediator {
public:
    Mediator() {}
    virtual ~Mediator() {}

    virtual void buttonChanged(Widget *) = 0;
};

然后定义 ButtonLineEdit 的共同基类 Widget, 它需要知道谁来协调它 Mediator, 并且在点击的时候, 通知 Mediator, 本来这里应该是一个类似 MouseEvent 事件的函数, 在这个函数中调用 m_mediator->buttonChanged(this);, 但是这只是一个例子, 我们就简单一点

#include "mediator.h"

class Widget {
public:
    Widget(Mediator *mediator) : m_mediator(mediator) {}
    virtual ~Widget(){}

    virtual void Click() {
        m_mediator->buttonChanged(this);
    }

private:
    Mediator *m_mediator;
};

接下来, 我么先将具体的 ButtonLineEdit 实现出来

#include "widget.h"
#include <string>
using namespace std;

class Button : public Widget {
public:
    Button(Mediator *mediator, string showinfo) : Widget(mediator), m_showinfo(showinfo) {}

    void setShowInfo(string info) { m_showinfo = info;}
    string getShowInfo() { return m_showinfo;}

private:
    string m_showinfo;
};

class LineEdit : public Widget {
public:
    LineEdit(Mediator *mediator) : Widget(mediator), m_showinfo(""){}

    void setShowInfo(string info) { m_showinfo = info;}
    string getShowInfo() { return m_showinfo;}

private:
    string m_showinfo;
};

接下来就是具体的中介者, 因为它需要统筹, 我就让它直接初始化 2 个按键和 LineEdit, 顺便添加了2个模拟点击的接口和一个显示 LineEdit 值的接口

#define SAFE_DELETE(p) if(p) {delete p; p=nullptr;}
#include "concretecolleague.h"
#include <iostream>
using namespace std;

class ConcreteMediator : public Mediator {
public:
    ConcreteMediator() :
        m_number1(new Button(this, string("1")))
      , m_number2(new Button(this, string("2")))
      , m_lineedit(new LineEdit(this))
    {}

    ~ConcreteMediator() {
        SAFE_DELETE(m_number1);
        SAFE_DELETE(m_number2);
        SAFE_DELETE(m_lineedit);
    }

    void clickButton1() {
        m_number1->Click();
    }

    void clickButton2() {
        m_number2->Click();
    }

    void showLineEditInfo() {
        cout << m_lineedit->getShowInfo() << endl;
    }

    virtual void buttonChanged(Widget *widget) {
        string new_info = m_lineedit->getShowInfo();

        if (widget == m_number1) {
            string info = m_number1->getShowInfo();
            new_info += info;
        } else if (widget == m_number2) {
            string info = m_number2->getShowInfo();
            new_info += info;
        }

        m_lineedit->setShowInfo(new_info);
    }

private:
    Button *m_number1;
    Button *m_number2;
    LineEdit *m_lineedit;
};

ps: 这里 buttonChanged(Widget *widget) 函数中在这个例子是可以不用 if...else... 这种形式, 因为使用的接口都是一样

最后就是 main 函数的调用

#include "concretemediator.h"

int main()
{
    ConcreteMediator mediator;

    mediator.clickButton1();
    mediator.clickButton1();
    mediator.clickButton1();
    mediator.showLineEditInfo();

    mediator.clickButton2();
    mediator.clickButton2();
    mediator.clickButton2();
    mediator.clickButton2();
    mediator.showLineEditInfo();

    return 0;
}

运行结果

111
1112222

总结

将复杂的交互部分整理成一个中介对象, 由中介对象来承担复杂的变化, 但是让它统筹的对象达到松耦合的目标, 整体结构也更加灵活~