设计模式-行为模式-中介者模式

11 Jun 2021

Reading time ~27 minutes

意图

中介者模式是一种行为设计模式,能让你减少对象之间混乱无序的依赖关系。该模式会限制对象之间的直接交互,迫使它们通过一个中介者对象进行合作。

image-20210611142814486

问题

假如你有一个创建和修改客户资料的对话框,它由各种控件组成,例如文本框(TextField)、复选框(Checkbox)和按钮(Button)等。

image-20210611143001269

某些表单元素可能会直接进行互动。例如,选中“我有一只狗”复选框后可能会显示一个隐藏文字框用于输入狗狗的名字。另一个例子是提交按钮必须在保存数据前校验所有输入内容。

image-20210611143111292

如果直接在表单元素代码中实现业务逻辑,你将很难在程序其他表单中复用这些元素类。例如,由于复选框类与狗狗的文本框相耦合,所以将无法在其他表单中使用它。你要么使用渲染资料表单时用到的所有类,要么一个都不用。

解决方案

中介者模式建议你停止组件之间的直接交流并使其相互独立。这些组件必须调用特殊的终结者对象,通过中介者对象重定向调用行为,以间接的方式进行合作。最终,组件仅依赖于一个中介者类,无需与多个其他组件相耦合。

在资料编辑表单的例子中,对话框(Dialog)类本身将作为中介者,其很可能已知自己所有的子元素,因此你甚至无需在该类中引入新的依赖关系。

image-20210611143541332

绝大部分重要的修改都在实际表单元素中进行。让我们想想提交按钮。之前,当用户点击按钮后,它必须对所有表单元素数值进行校验。而现在它的唯一工作是将点击事件通知给对话框。收到通知后,对话框可以自行校验数值或将任务委派给各元素。这样一来,按钮不再与多个表单元素相关联,而仅依赖于对话框类。

你还可以为所有类型的对话框抽取通用接口,进一步削弱其依赖性。接口中将声明一个所有表单元素都能使用的通知方法,可用于将元素中发生的时间通知给对话框。这样一来,所有实现了该接口的对话框都能使用这个提交按钮了。

采用这种方法,中介者模式让你能在单个终结者对象中封装多个对象间的复杂关系网。类所拥有的依赖关系越少,就越易于修改、扩展或复用。

真实世界类比

image-20210611144037259

飞行器驾驶员们在靠近或离开空中管制区域时不会直接相互交流。但他们会与飞机跑道附近,塔台中的空管员通话。如果没有空管员,驾驶员就需要留意机场附近的所有飞机,并与数十位飞行员组成的委员会讨论降落顺序。那恐怕会让飞机坠毁的统计数据一飞冲天吧。

塔台无需管制飞行全程,只需在航站区加强管控即可,因为该区域的决策参与者数量对于飞行员来说实在太多了。

中介者模式结构

image-20210611144326454

中介者模式适合应用场景

当一些对象和其他对象紧密耦合以致难以对其进行修改时,可使用中介者模式。

该模式让你将对象间的所有关系抽取成为一个单独的类,以使对于特定组件的修改工作独立于其他组件。

当组件因过于以来其他组件而无法在不同应用中复用时,可使用中介者模式。

应用中介者模式后,每个组件不再知晓其他组件的情况。尽管这些组件无法直接交流,但它们仍可通过中介者对象进行间接交流。如果你希望在不同应用中复用一个组件,则需要为其提供一个新的中介者类。

如果为了能在不同情境下复用一些基本行为,导致你需要被迫创建大量组件子类时,可使用中介者模式。

由于所有组件间关系都被包含在中介者中,因此你无需修改组件就能方便地新建中介者类以定义新的组件合作方式。

实现方式

  1. 找到一组当前紧密耦合,且提供其独立性能带来更大好处的类(例如更易于维护或更方便复用)。
  2. 声明中介者接口并描述中介者和各种组件之间所需的交流接口。在绝大多数情况下,一个接收组件通知的方法就足够了。如果你希望在不同情景下复用组件类,那么该接口将非常重要。只要组件使用通用接口与其中介者合作,你就能将改组件与不同实现中的中介者进行连接。
  3. 实现具体中介者类。该类可从自行保存其下所有组件的引用中受益。
  4. 你可以更进一步,让中介者负责组建对象的创建和销毁。此后,中介者可能会与工厂或外观类似。
  5. 组件必须保存对于中介者对象的引用。该连接通常在组件的构造函数中建立,该函数会将中介者对象作为参数传递。
  6. 修改组件代码,使其可调用中介者的通知方法,而非其他组件的方法。然后将调用其他组件的代码抽取到中介者类中,并在中介者接收到该组件通知时执行这些代码。

中介者模式优缺点

优点:

  • 单一职责原则。你可以将多个组件间的交流抽取到同一位置,使其更易于理解和维护。
  • 开闭原则。你无需修改实际组件就能增加新的中介者。
  • 你可以减轻应用中多个组件间的耦合情况。
  • 你可以更方便地复用各个组件。

缺点:

  • 一段时间后,中介者可能会演化成为上帝对象。

与其他模式的关系

  • 责任链模式、命令模式、中介者模式和观察者模式用于处理请求发送者和接收者之间的不同连接方式:

    • 责任链按照顺序来请求动态传递给一系列的潜在接收者,直至其中一名接收者对请求进行处理
    • 命令在发送者和请求者之间建立单向连接
    • 中介者清除了发送者和请求者之间的直接连接,强制它们通过一个中介对象进行间接沟通
    • 观察者允许接收者动态地订阅或取消接受请求
  • 外观和中介者模式的职责类似: 它们都尝试在大量紧密耦合的类中组织起合作。

    • 外观为子系统中的所有对象定义了一个简单接口, 但是它不提供任何新功能。 子系统本身不会意识到外观的存在。 子系统中的对象可以直接进行交流。
    • 中介者将系统中组件的沟通行为中心化。 各组件只知道中介者对象, 无法直接相互交流。
  • 中介者和观察者之间的区别往往很难记住。 在大部分情况下, 你可以使用其中一种模式, 而有时可以同时使用。 让我们来看看如何做到这一点。

    中介者的主要目标是消除一系列系统组件之间的相互依赖。 这些组件将依赖于同一个中介者对象。 观察者的目标是在对象之间建立动态的单向连接, 使得部分对象可作为其他对象的附属发挥作用。

    有一种流行的中介者模式实现方式依赖于观察者。 中介者对象担当发布者的角色, 其他组件则作为订阅者, 可以订阅中介者的事件或取消订阅。 当中介者以这种方式实现时, 它可能看上去与观察者非常相似。

    当你感到疑惑时, 记住可以采用其他方式来实现中介者。 例如, 你可永久性地将所有组件链接到同一个中介者对象。 这种实现方式和观察者并不相同, 但这仍是一种中介者模式。

    假设有一个程序, 其所有的组件都变成了发布者, 它们之间可以相互建立动态连接。 这样程序中就没有中心化的中介者对象, 而只有一些分布式的观察者。

以上摘自REFACTORING GURU

代码示例

飞机接口:

public interface Aircraft {
    /**
     * 降落
     */
    void landfall();
}

直升机:

public class Helicopter implements Aircraft{
    private Tower tower;

    public Helicopter(Tower tower) {
        this.tower = tower;
    }

    @Override
    public void landfall() {
        if(tower.indicate()){
            System.out.println("直升机可以降落...");
        }else{
            System.out.println("直升机盘旋等待降落...");
        }
    }
}

飞机:

public class Plane implements Aircraft{
    private Tower tower;

    public Plane(Tower tower) {
        this.tower = tower;
    }

    @Override
    public void landfall() {
        if(tower.indicate()){
            System.out.println("飞机可以降落...");
        }else{
            System.out.println("飞机盘旋等待降落...");
        }
    }
}

塔台:

public class Tower {
    private int limit;

    public boolean indicate(){
        if(limit < 3){
            limit++;
            return true;
        }else{
            return false;
        }
    }
}

客户端代码:

public class Demo {
    public static void main(String[] args) {
        Tower tower = new Tower();
        Helicopter helicopter = new Helicopter(tower);
        Plane p1 = new Plane(tower);
        Plane p2 = new Plane(tower);
        Plane p3 = new Plane(tower);
        helicopter.landfall();
        p1.landfall();
        p2.landfall();
        p3.landfall();
    }
}

//直升机可以降落...
//飞机可以降落...
//飞机可以降落...
//飞机盘旋等待降落...


Design PatternsProfessional Knowledge Share Tweet +1