中介者模式

<image>

在软件工程领域，中介者模式（mediator pattern），定义了一个中介者对象，该对象封装了系统中对象间的交互方式。由于它可以在运行时改变程序的行为，这种模式是一种行为型模式。

## 概述

中介者模式是23个周知模式（即GoF《设计模式》）中的一个，GoF设计模式旨在提供重复出现的设计问题的解决方案，以编写灵活和可复用的面向对象软件。也就是说，使对象更加易于实现、修改、测试和复用。

通常程序由大量的类组成，这些类中包含程序的逻辑和运算。然而，当开发者将更多的类加入到程序中之后，类间交互关系可能变得更为复杂，这会使得代码变得更加难以阅读和维护，尤其是在重构的时候。此外，程序将会变得难以修改，因为对其所做的任何修改都有可能影响到其它几个类中的代码。

中介者模式是为了“定义一个封装了对象间交互关系的对象”。这种方式避免了显式调用其他类，促进了类间的松耦合，并且使得类间交互关系本身可以单独修改。客户类可以使用中介者向其他客户类发送信息，并且通过中介者引发的事件收到信息。

在中介者模式中，“同事”对象间的通信过程被封装在一个“中介者”（调解人）对象之中。对象之间不再直接交互，而是通过调解人进行交互。这么做可以减少可交互对象间的依赖，从而降低耦合。

中介者设计模式可以解决如下问题：避免一组相互交互的对象之间出现紧耦合，能够独立地改变一组对象之间的交互关系而不影响其他对象。使用直接逐个访问并更新彼此的方式进行对象间的交互灵活性低，因为这种方式使对象彼此间紧密耦合，导致不可能单独修改类间交互关系本身，而不影响关系中进行交互的类。并且这种方式会令对象变得无法复用，并且难以测试。由于“紧耦合”的对象过多了解其他对象的内部细节，这种对象难以实现、修改、测试以及复用。

中介者模式解决上述问题的方式：定义一个独立的中介者(调解员)的对象，封装一组对象之间的交互关系；对象将自己的交互委托给中介者执行，避免直接与其他对象进行交互。对象利用中介者对象与其他对象进行间接交互，中介者对象负责控制和协调交互关系，这么做可使得对象间“松耦合”。这些对象只访问中介者，不了解其他对象的细节。

## 结构

<image>

在以上UML类图中，类`Colleague1`和`Colleague2`不引用和更新彼此。相反，它们引用一个通用的`Mediator`接口以实现类间交互的控制和协调(通过`mediate()`)，这使得它们独立于类间交互的具体细节。类`Mediator1`实现了类`Colleague1`和`Colleague2`之间的交互细节。

上述UML时序图显示了这些类之间在运行时的交互过程：在这个例子中，一个`Mediator1`对象中介，即控制和协调了`Colleague1`和`Colleague2`的对象之间的交互。

如果`Colleague1`的对象需要与`Colleague2`的对象进行交互，例如更新或者同步状态，`Colleague1`调用`Mediator1`对象的`mediate(this)`方法，以此得到对`Colleague1`对象（自己）的改动信息，并且在`Colleague2`的对象上执行`action2()`方法。

此后，`Colleague2`对象调用`Mediator1`对象的`mediate(this)`方法，得到对`Colleague2`（自己）的修改的数据，并且对`Colleague1`对象调用`action1()` 方法。

### 参与者

- Mediator：定义 Colleague对象之间的交互接口。
- ConcreteMediator，比如`Mediator1`：实现了Mediator接口，并且协调Colleague对象之间的交互，知道所有的Colleagues对象以及其交互目的。
- Colleague：定义了通过Mediator与其他Colleague对象交互的接口。
- ConcreteColleague，比如`Colleague1`和`Colleague2`：实现了Colleague接口，并且通过它的Mediator与其他Colleague交互。

## 范例

### Java

在以下示例中，一个中介者对象控制了三个互相交互的按钮的状态，为此它有设置状态的三个方法：`book()`, `view()` 和 `search()`。当相应的按钮被激活时，对应的方法通过`execute()`方法被调用。

于是这里在交互中每个交互的参与者（本例中即按钮）将自己的行为提交给中介者并且由中介者将这些行为转给对应的参与者。

```java
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

//Colleague interface
interface Command {
    void execute();
}

//Abstract Mediator
interface Mediator {
    void book();
    void view();
    void search();
    void registerView(BtnView v);
    void registerSearch(BtnSearch s);
    void registerBook(BtnBook b);
    void registerDisplay(LblDisplay d);
}

//Concrete mediator
class ParticipantMediator implements Mediator {
    BtnView btnView;
    BtnSearch btnSearch;
    BtnBook btnBook;
    LblDisplay show;

    //....
    public void registerView(BtnView v) {
        btnView = v;
    }

    public void registerSearch(BtnSearch s) {
        btnSearch = s;
    }

    public void registerBook(BtnBook b) {
        btnBook = b;
    }

    public void registerDisplay(LblDisplay d) {
        show = d;
    }

    public void book() {
        btnBook.setEnabled(false);
        btnView.setEnabled(true);
        btnSearch.setEnabled(true);
        show.setText("booking...");
    }

    public void view() {
        btnView.setEnabled(false);
        btnSearch.setEnabled(true);
        btnBook.setEnabled(true);
        show.setText("viewing...");
    }

    public void search() {
        btnSearch.setEnabled(false);
        btnView.setEnabled(true);
        btnBook.setEnabled(true);
        show.setText("searching...");
    }
}

//A concrete colleague
class BtnView extends JButton implements Command {
    Mediator med;

    BtnView(ActionListener al, Mediator m) {
        super("View");
        addActionListener(al);
        med = m;
        med.registerView(this);
    }

    public void execute() {
        med.view();
    }
}

//A concrete colleague
class BtnSearch extends JButton implements Command {
    Mediator med;

    BtnSearch(ActionListener al, Mediator m) {
        super("Search");
        addActionListener(al);
        med = m;
        med.registerSearch(this);
    }

    public void execute() {
        med.search();
    }
}

//A concrete colleague
class BtnBook extends JButton implements Command {
    Mediator med;

    BtnBook(ActionListener al, Mediator m) {
        super("Book");
        addActionListener(al);
        med = m;
        med.registerBook(this);
    }

    public void execute() {
        med.book();
    }
}

class LblDisplay extends JLabel {
    Mediator med;

    LblDisplay(Mediator m) {
        super("Just start...");
        med = m;
        med.registerDisplay(this);
        setFont(new Font("Arial", Font.BOLD, 24));
    }
}

class MediatorDemo extends JFrame implements ActionListener {
    Mediator med = new ParticipantMediator();

    MediatorDemo() {
        JPanel p = new JPanel();
        p.add(new BtnView(this, med));
        p.add(new BtnBook(this, med));
        p.add(new BtnSearch(this, med));
        getContentPane().add(new LblDisplay(med), "North");
        getContentPane().add(p, "South");
        setSize(400, 200);
        setVisible(true);
    }

    public void actionPerformed(ActionEvent ae) {
        Command comd = (Command) ae.getSource();
        comd.execute();
    }

    public static void main(String[] args) {
        new MediatorDemo();
    }
}
```

### C#

中介者模式确保了构件之间的松耦合，使得它们不直接调用彼此，而是通过中介者调用彼此。在下面的例子中，调解人注册了所有的构件，然后调用了它们的`SetState`方法。

```csharp
public interface IComponent {
    void SetState(object state);
}

public class Component1 : IComponent {
    public void SetState(object state) {
        throw new NotImplementedException();
    }
}

public class Component2 : IComponent {
    public void SetState(object state) {
        throw new NotImplementedException();
    }
}

// Mediates the common tasks
public class Mediator {
    public IComponent Component1 { get; set; }
    public IComponent Component2 { get; set; }

    public void ChangeState(object state) {
        this.Component1.SetState(state);
        this.Component2.SetState(state);
    }
}
```

一个聊天室，或者一个需要客户彼此间每次发送消息之后执行动作的系统（对聊天室来说就是每个人之间发送消息）可以使用中介者模式。但实际上对聊天室使用中介者模式只在包含远程功能时有用。原生的套接字不允许委托回呼函式（即通过中介者类订阅的`MessageReceived`事件）。

```csharp
public delegate void MessageReceivedEventHandler(string message, string from);

public class Mediator {
    public event MessageReceivedEventHandler MessageReceived;

    public void Send(string message, string from) {
        if (MessageReceived != null) {
            Console.WriteLine("Sending '{0}' from {1}", message, from);
            MessageReceived(message, from);
        }
    }
}

public class Person {
    private Mediator _mediator;

    public string Name { get; set; }

    public Person(Mediator mediator, string name) {
        Name = name;
        _mediator = mediator;
        _mediator.MessageReceived += new MessageReceivedEventHandler(Receive);
    }

    private void Receive(string message, string from) {
        if (from != Name)
            Console.WriteLine("{0} received '{1}' from {2}", Name, message, from);
    }

    public void Send(string message) {
        _mediator.Send(message, Name);
    }
}
```

### Python

下面是Python的例子：

```python
from abc import ABC, abstractmethod
from weakref import WeakKeyDictionary, WeakValueDictionary

class Mediator(ABC):
    def __init__(self):
        self.__coll_name_dict = WeakValueDictionary()
        self.__coll_id_dict = WeakKeyDictionary()
    def __call__(self, coll_name):
        return self.__coll_name_dict[coll_name]
    def register(self, coll):
        mem = lambda:None
        coll.colleague_id = mem
        self.__coll_name_dict[coll.name] = mem
        self.__coll_id_dict[mem] = coll
    def unregister(self, coll):
        del self.__coll_name_dict[coll.name]
        del self.__coll_id_dict[coll.colleague_id]
        del coll.colleague_id
    @abstractmethod
    def mediate(self, src, dest_id, *args, **kwargs): 
        self.__coll_id_dict[dest_id].action(*args, **kwargs)

class MediatorA(Mediator):
    def __init__(self):
        super().__init__()
        self.__lock = False
    def mediate(self, src, dest_id):
        if self.__lock is False:
            self.__lock = True
            ps, kw = src.get_state()
            kw['Sender_ID'] = src.colleague_id
            super().mediate(src, dest_id, *ps, **kw)
            self.__lock = False

class Colleague(ABC):
    def __init_subclass__(cls, /, coll_id=None, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.colleague_id = coll_id
    @abstractmethod
    def __init__(self, mediator, name):
        self.mediator = mediator
        self.name = name
    @abstractmethod
    def get_state(self): pass
    @abstractmethod
    def set_state(self): pass
    @abstractmethod
    def action(self): pass
    def interact(self, other_id):
        self.mediator.mediate(self, other_id)

class ColleagueA(Colleague):
    def __init__(self, mediator, name, *args, **kwargs):
        super().__init__(mediator, name)
        self.state = args
        self.kwstate = kwargs
        mediator.register(self)
    def get_state(self):
        return self.state, self.kwstate
    def set_state(self, *args, **kwargs):
        self.state = args
        self.kwstate = kwargs
    def action(self, *args, **kwargs):
        print(self, "Got", args, kwargs, "From", self.mediator)
```

这里假定了能保证`colleague`对象之间不可获得到其他`colleague`对象的直接引用，只能通过其他`colleague`对象的ID来与其进行交互。`colleague`对象可以通过向`mediator`对象检索其他`colleague`对象的名字来获得其ID，或者从其他`colleague`对象发来的交互信息中提取其ID。这里用作ID的对象在例子中只拥有充当唯一标识符的一项简单功能。下面是其执行：

```pycon
>>> mediator = MediatorA()
>>> colleague1 = ColleagueA(mediator, 'colleague_01')
>>> colleague2 = ColleagueA(mediator, 'colleague_02')
>>> colleague1.set_state("test", kw="text")
>>> colleague1.interact(mediator('colleague_02'))
<__main__.ColleagueA object at 0x757e6b147610> Got ('test',) {'kw': 'text', 'Sender_ID': <function Mediator.register.<locals>.<lambda> at 0x757e6afe2700>} From <__main__.Mediator object at 0x757e6afde3c0>
```

## 外部链接

- Mediator Design Pattern
- Is the use of the mediator pattern recommend? - Stack Overflow