Skip to content

Latest commit

 

History

History
146 lines (111 loc) · 11.2 KB

Mediator.md

File metadata and controls

146 lines (111 loc) · 11.2 KB

行为型 - 中介者(Mediator)设计模式


中介者模式很简单,它描述了当我们遇到多个对象之间存在复杂的依赖关系时,该如何降低这些依赖关系之间的耦合性。用一句时髦的话来形容中介者模式,那就是没有什么问题是加一层解决不了的。

一、问题引入

当我们使用面向对象的思维来构建应用模型时,总是免不了要处理各个对象之间的依赖关系。在一个职责分明的系统设计中,依赖是一定存在的,并且职责越是分明,依赖或许越复杂。因为在面向对象设计时,我们总是强调对象行为的单一性,不应把过多的职责赋予单个对象。所以现实世界中用户的一个动作,却需要经过一系列对象进行处理,他们就像是流水线上的工人,彼此协同才能完成工作。

这里并不是在指责这种设计有问题,如果你看过本系列中关于门面模式的介绍,那么你或许对我们描述的场景有一些印象。在那里,我们说道:多个对象之间的较为复杂的依赖关系往往表明系统设计的足够好,因为那意味着不同的行为被拆分不同的对象中负责。让我们以一个例子来说明这种情况。

在各个 IDE 中,都有设置编辑器字体的功能,在 Intellij-IDEA 中,字体设置页面如下所示:

在用户选择字体后,对应的编辑器框内文本字体将根据选择的内容发生变化。除此之外,针对每一种字体都提供了默认的排版设置(Typography Settings),所以在字体发生变化时,排版设置可能发生相应的变化。而在设置编辑器内文本内容的字体时,我们还需要知道用户设置的字体大小、行高等等信息。

这个例子稍显复杂,让我们定义一个简化版的实现作为演示案例。该简化版实现中的组件列表如下:

  • 1) 可选字体列表:当切换字体时,设置提供默认的字体大小、是否加粗属性,并且编辑框内文本切换为所选字体;
  • 2) 字体输入框:可输入关键字对字体列表进行筛选,筛选时,除可选字体列表外,其他的组件均不可用;
  • 3) 字体大小调整框:当字体的大小值发生变化时,编辑框内文本字体的大小相应切换;
  • 4) 字体加粗单选框:切换编辑框内文本字体的加粗样式;
  • 5) 编辑框:在字体切换时,用于展示当前所选字体的实际效果。当编辑框内没有任何文本时,其他组件均不可用。

整理各个组件之间的交互逻辑后,我们得到各个组件之间的关系图如下:

我们看到,各个对象之间复杂的依赖关系错综复杂。更糟糕的是当新的组件加进来时,整个结构将变得更加难以维护。而此时就是引入中介者模式的最佳时机。

二、解决方案

中介者模式建议我们用一个中介对象来封装一系列的对象交互,在中介者对象中处理多个对象之间复杂的交互。各个对象之间不直接进行通信,由中介者对象来协调各个对象的工作。引入中介者对象后,各个组件之间的关系图将变成如下所示:

可以看出引入中介者对象之后,使得之前对象之间复杂的连接关系变得相当简单。如果要往这个模型中加入一个新的部件,我们只需要让新的部件连接到中介者对象即可。

按照引入中介者对象之后模型,以字体列表中切换字体为例,用户发起的动作的交互过程如下所示:

正如我们在之前所描述的那样:中介者对象负责控制和协调各个部件之间的交互,它的存在使得各个对象之间不需要再相互引用。相比来看,中介者模式多了中介者对象,并且各个部件之间不再有显式依赖。

三、案例实现

我已经实现了该案例,为了在阅读代码时有一个清晰的结构脉络,这里先介绍一下该案例的类图结构。

3.1 案例类图

案例的类图结构如上图所示,因篇幅原因,该类图中仅罗列出主要的方法,更详细的方法参考请完整代码。对于类图中类的解释如下:

  • AbstractWidget:抽象部件,维护了一个 page 对象(中介者),以便子类在需要时可将自身状态的改变通知给中介者,由中介者协调其他的组件响应用户的动作。setEnable()setDisable()方法为部件的禁用和启用方法;
  • FontInput:字体输入框部件。提供了getCurrentText()setFontName()方法,分别表示获取输入框内当前的文本、设置输入框内的内容;
  • OptionalList:字体列表框部件。提供了selectFontName()filter()方法,分别为获取当前所选字体和对列表的可选项进行过滤;
  • SizeInput:字体大小调整框部件。提供了获取字体大小、设置字体大小的方法;
  • BoldInput:字体加粗单选框部件。提供了获取字体加粗样式、设置字体加粗样式的方法;
  • Editor:编辑框。提供了isEmptyText()changeFont()方法,前者判断当前编辑框内的文本是否为空,后者为改变编辑框内的文本字体;
  • Page:页面(中介者)。维持了该页面的所有部件的引用,并且提供了一个widgetChanged()方法,当页面内的某个组件状态发生变化时,可调用该方法通知自己,在该方法中协调与其他组件之间的交互。例如,OptionalList.valueChanged()Editor.removeUpdate()等方法的触发就代表该组件的状态发生了变化。

3.2 代码附录

代码层次及类说明如上所示,更多内容请参考案例代码。客户端示例代码如下

public class Application {
    public static void main(String[] args) {
        JFrame frame = new JFrame("FontSelector");
        frame.setLocationRelativeTo(null);
        frame.setResizable(false);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(600, 450);
        // 添加页面
        OptionalList fontList = new OptionalList();
        FontInput fontInput = new FontInput();
        SizeInput sizeInput = new SizeInput();
        BoldInput boldInput = new BoldInput();
        Editor displayBox = new Editor();
        
        Page page = new Page(fontList, fontInput, sizeInput, boldInput, displayBox);
        fontList.setPage(page);
        fontInput.setPage(page);
        sizeInput.setPage(page);
        boldInput.setPage(page);
        displayBox.setPage(page);

        frame.getContentPane().add(page, BorderLayout.CENTER);
        frame.setVisible(true);
    }
}

运行结果如下

四、中介者模式

4.1 意图

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

中介者模式的目的在于解耦对象之间复杂的依赖关系,中介者对象的存在使得各个对象不需要再依赖于多个其他的对象。对象只需要把自身无法完成的工作委托给中介者,由中介者对象负责协调其他的对象进行交互。

4.2 类图分析

通常来说,典型的中介者模式的类图结构如下所示:

中介者模式的参与者有如下:

  • Colleague:抽象同事。因为各个部件之间的关系就像同事一样的相互协作,所以通常将部件称呼为同事。所有的同事自身都维护了一个指向中介者的引用;
  • ConcreteColleague:同事类。在合适的时机(自身无法完成工作时),将工作委托给中介者对象;
  • Mediator:抽象中介者。定义一个接口用于各同事对象的协作,当只有一个中介者时,可省略该接口;
  • ConcreteMediator:具体中介者。负责维护所有同事,通过协调各对象实现协作行为。

整个工作流程可简述为:某个同事对象向中介者发起一个请求,中介者协调其他的同事(调用其他同事对象的相关方法)来共同完成协作行为。

五、深入理解

5.1 特点

(1)简化了工作模式

在开篇时,我们说各个组件(同事对象)之间的依赖关系错综复杂,一个组件和其他的组件之间的关系可能是多对多的关系。但是使用了中介者模式之后,这个耦合关系被降低成了一对多(一个中介者 -> 多个组件),这种工作模式使得整个系统的结构更加简单,更容易维护。

(2)突出关注点

很多时候,各个对象之间的调用链并不简单,这个调用链可能相当长。如果想要弄清楚系统对于用户的一个动作到底有哪些对象协作完成,我们只能沿着调用链逐个追踪。使用中介者模式则不一样,中介者对象中就已经包含了各个组件在何时开始处理请求,我们可以很轻松的就把注意力转移到各个对象之间的协作上来。所以,使用中介者模式有助于让我们弄清楚一个系统中的对象究竟是如何交互的。

(3)控制集中化

中介者对象封装了所有对象之间的交互逻辑,这可能使得中介者对象相当庞大且复杂。所以,使用时应当慎重考虑,如果不能接受中介者对象在日复一日的维护中变成庞然大物,那就不应该使用中介者模式。

5.2 使用技巧

(1)抽象的 Mediator 并不是必须的

当各 Colleague 仅与一个 Mediator 一起工作时,没有必要定义一 个抽象的 Mediator 类。

5.3 扩展

对于中介者模式来说,因为其将控制集中化,可能为系统引入一些庞大的中介对象,这使得我们在实际中使用时必须非常小心。但是其蕴含的思想却值得我们借鉴,比如我们常用的消息中间件(MQ)就与中介者模式的思想不谋而合。消息中间件的作用点之一就是解耦,正如中介者模式一样,消息的生产者并不关心由谁来消费消息,也不负责与消费者进行交互,他仅仅只是将消息发送到消息队列中,由消息对象负责将该消息转发给合适的消费者。并且,消息生产者同时也可以是另一种消息的消费者,正如在案例中描述的那样:当字体输入框中切换字体时,编辑器相应的切换自身内容的字体;而当编辑器内的内容为空时,反过来将导致字体输入框不可用。

附录

回到主页案例代码