中介者模式 调停者 Mediator 行为型 设计模式(二十一)
中介者模式(Mediator)
调度、调停
我们以装修为例 一般装修公司都会给每一个项目配备一个项目经理(这个项目也就是你家这个单子了,项目经理就是包工头) 装修的一般阶段分为:前期设计→拆改→水电→瓦工→木工→油漆→安装→保洁→软装 项目经理手上经常同时有几个工地在同步进行,只要错的开就好了 因为每个阶段都是有先后顺序的,你不可能先木工,然后再去拆改; 因为每个阶段也都需要一定时间,也意味着这一拨人不可能同时在你家工作 开工后项目经理会进行工作安排 水电工结束了A之后,项目经理会安排他到B,然后安排瓦工到A,然后........ 所有的顺序都是由项目经理负责调度,水电工可以完全不认识瓦工,他们也完全不需要进行联系 有事儿找项目经理 如果没有项目经理,会是什么场景? 那就是人人都是项目经理,人人都需要管自己,还需要管别人 也就是每个人安排分配自己的时间与任务 水电工结束后需要联系瓦工进场,如果瓦工发现有遗留问题,需要联系水电工进行沟通 木工需要联系瓦工确认进展情况,油漆工又需要确认木工状况... 你会发现他们必须要经常保持联系,以获得进展情况,进而安排自己的工作 一个包工队尚且如此,如果是一个大的装修公司,怎么办? 而且装修而言,阶段之间还会有顺序,油漆工用不到联系水电工 但是在系统中,对象岂会仅仅与一个对象联系? 那岂不是更复杂、乱套?
中介者模式就是为了解决系统内部的调度问题,降低系统内部各模块之间的耦合度。 装修公司的项目经理、小组组长、班长,团队leader等其实这都是中介者模式的体现。 有很多书中以“房屋中介”作为中介者模式的一种场景 个人认为对于某一个房东或者租客而言,“房屋中介”的含义是为你服务的中介人员,此时的含义更接近代理模式 而从广义上看,有很多租客、买家,也存在很多房东,“房屋中介”将他们联系在一起,此时的“房租中介”应该是中介公司,这时才更符合中介者模式的含义 中介者模式的重点在于“调度、协调”,含义更接近“指挥中心”,被指挥的是该系统内部的成员 如果在一个系统中对象之间存在多对多的相互关系 我们可以将对象之间的一些交互行为从各个对象中分离出来,并集中封装在一个中介者对象中,并由该中介者进行统一协调 如上图所示,对象之间多对多的复杂关系就转化为相对简单的一对多关系 简化了对象之间的复杂交互 显然,中介者模式是迪米特法则(不要和陌生人说话)的典型。
意图
用一个中介对象(中介者)来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散 而且可以独立地改变它们之间的交互。 中介者模式又称为调停者模式。 面向对象的程序设计中,我们通常将功能进行分解,按照职责以类为维度进行划分,也就是使用时功能最终将分布在多个对象中 并且我们会尽可能的保持对象功能的单一(单一职责原则) 相对于对象的单一职责来说,任何的系统或者模块的功能却并不会单一,往往都是有多个对象交互协作来实现所有的功能 对象之间不可避免的需要建立连接 换句话说 系统(或者某模块)必然是复杂的(没有什么系统可以几个对象就轻松搞定,那样或许也不能称之为系统了吧) 功能必然会分布在多个对象中 多个对象协作必然需要联系,这必然导致耦合的产生 如上图所示,虽然系统对外呈现是一个统一的整体,但是,内部各个模块之间很可能是紧密的耦合 各个模块相互联系,可能互相持有引用,会出现网状结构,完全不符合迪米特法则。 如果对系统进行改动,将会变得困难。我们以装修为例 一般装修公司都会给每一个项目配备一个项目经理(这个项目也就是你家这个单子了,项目经理就是包工头) 装修的一般阶段分为:前期设计→拆改→水电→瓦工→木工→油漆→安装→保洁→软装 项目经理手上经常同时有几个工地在同步进行,只要错的开就好了 因为每个阶段都是有先后顺序的,你不可能先木工,然后再去拆改; 因为每个阶段也都需要一定时间,也意味着这一拨人不可能同时在你家工作 开工后项目经理会进行工作安排 水电工结束了A之后,项目经理会安排他到B,然后安排瓦工到A,然后........ 所有的顺序都是由项目经理负责调度,水电工可以完全不认识瓦工,他们也完全不需要进行联系 有事儿找项目经理 如果没有项目经理,会是什么场景? 那就是人人都是项目经理,人人都需要管自己,还需要管别人 也就是每个人安排分配自己的时间与任务 水电工结束后需要联系瓦工进场,如果瓦工发现有遗留问题,需要联系水电工进行沟通 木工需要联系瓦工确认进展情况,油漆工又需要确认木工状况... 你会发现他们必须要经常保持联系,以获得进展情况,进而安排自己的工作 一个包工队尚且如此,如果是一个大的装修公司,怎么办? 而且装修而言,阶段之间还会有顺序,油漆工用不到联系水电工 但是在系统中,对象岂会仅仅与一个对象联系? 那岂不是更复杂、乱套?
中介者模式就是为了解决系统内部的调度问题,降低系统内部各模块之间的耦合度。 装修公司的项目经理、小组组长、班长,团队leader等其实这都是中介者模式的体现。 有很多书中以“房屋中介”作为中介者模式的一种场景 个人认为对于某一个房东或者租客而言,“房屋中介”的含义是为你服务的中介人员,此时的含义更接近代理模式 而从广义上看,有很多租客、买家,也存在很多房东,“房屋中介”将他们联系在一起,此时的“房租中介”应该是中介公司,这时才更符合中介者模式的含义 中介者模式的重点在于“调度、协调”,含义更接近“指挥中心”,被指挥的是该系统内部的成员 如果在一个系统中对象之间存在多对多的相互关系 我们可以将对象之间的一些交互行为从各个对象中分离出来,并集中封装在一个中介者对象中,并由该中介者进行统一协调 如上图所示,对象之间多对多的复杂关系就转化为相对简单的一对多关系 简化了对象之间的复杂交互 显然,中介者模式是迪米特法则(不要和陌生人说话)的典型。
结构
同事角色Colleague 系统中所有组成部件的抽象角色 具体的同事角色ConcreteColleague 系统的每一个具体的组成部分,就像公司的每个同事 提供自身职责功能的方法接口,供中介者调用 定义中介者到同事对象的接口,也就是提供接口给中介者调用 中介者(项目经理)根据你的技能分配任务,也就是调用你的方法 中介者角色Mediator 定义了同事Colleague对象到中介者的接口,也就是所有同事通信的接口(同事间的通信借助于中介者提供的这个方法) 也就是提供一个方法给同事们调用,用来请求其他同事协助协助,这个方法是中介者提供的 这个方法典型的示例就是事件处理方法 具体的中介者ConcreteMediator 具体的中介者,实现Mediator定义的接口,协调各同事进行协作 所有的成员之间,可以相互协调工作,但是却又不直接相互管理 这些对象都与项目经理“中介者”进行紧密联系 由项目经理进行工作协调,每个组成部分就如同我们项目组中的一个成员,也就是同事一样,这也是上文中Colleague 角色的由来 如何相互协调工作但是却又不直接相互管理?比如class A{ void f(){ //do sth B b = new B(); b.g(); }上面伪代码中 类A有一个方法f ,做了一些事情之后,创建了一个B的对象b,然后调用b的方法g,做了一些处理 这就是A与B的协作,A也同时具有管理B的职责 如果转换为下面的形式,就是中介者模式 A和B的协作不在具有对象管理关系,而是项目经理Mediator统一进行管理
class Mediator{ A a = new A(); B b = new B(); void cooperation(){ a.f(); b.g(); } }
代码示例
使用《设计模式 可复用面向对象软件的基础》中的例子为原型 考虑一个图形用户界面中对话框的实现。 对话框使用一个窗口来展现一系列的窗口组件,比如按钮菜单输入域等 比如下图,IDEA的字体设置窗口,当进行Font字体设置时- 预览区域内的字体将会发生变化
- 右下角的Apply 应用按钮将成为可点击状态
package mediator.simple; /** * 设置字体类,提供字体设置方法. * 并且创建展示Display对象,调用reDisplay方法重新展示 * 并且创建按钮Button对象,调用applyButton方法使能应用按钮 */ public class Font { public void setFont() { System.out.println("设置字体..."); Display display = new Display(); display.reDisplay(); Button button = new Button(); button.applyButton(); } }
package mediator.simple; public class Display { public void reDisplay() { System.out.println("字体重新展示..."); } }
package mediator.simple; public class Button { public void applyButton() { System.out.println("应用按钮可用..."); } }
package mediator.simple; public class Test { public static void main(String[] args) { Font font = new Font(); font.setFont(); } }上面的示例很简单 为了实现“点击设置字体,选择字体后预览框字体的改变以及使能应用按钮的功能” 也就是联动的功能 设置字体后,分别创建展示和按钮对象,调用对象的方法 很显然,字体不仅操心自己的事情,还管理着展示Display和按钮Button 而且,如果直接点击取消会发生什么?一切将会还原,又伴随着一系列的调用 难道仍旧需要:“不仅操心自己的事情,还要负责管理别人么”? 就像没有项目经理的包工队一样了,既操心自己又要管理别人 成了我们上面所说的网状结构,内部各个同事之间的耦合度极高
重构中介者模式
重构的业务逻辑:- 通过引入mediator中介者,作为同事之间协作的中间人,提供operation()方法,用于同事间请求协助、事件处理
- 每个同事类都知道这个中介,所以在抽象角色Colleague中设置了Mediator属性,构造方法注入,并且提供notifyEvent方法,封装了mediator的operation()方法
- 当具体的同事ConcreteColleague,执行操作后,需要其他同事协作时,直接调用notifyEvent()方法
- 每个具体的同事提供自身的职责接口
package mediator; public abstract class Mediator { abstract void operation(Colleague event); }Colleague抽象同事角色拥有Mediator,通过构造方法注入 提供了notifyEvent方法,调用中介者的operation方法,并且将自身作为参数
package mediator; public abstract class Colleague { private Mediator mediator; Colleague(Mediator mediator) { this.mediator = mediator; } public void notifyEvent() { mediator.operation(this); } }
package mediator; public class Button extends Colleague { Button(Mediator mediator){ super(mediator); } public void applyButton() { System.out.println("应用按钮可用..."); } }
package mediator; public class Display extends Colleague { Display(Mediator mediator) { super(mediator); } public void reDisplay() { System.out.println("字体重新展示..."); } }
package mediator; public class Font extends Colleague { private String fontName; public String getFontName() { return fontName; } Font(Mediator mediator) { super(mediator); } public void changeFont() { System.out.println("设置字体......"); fontName = "微软雅黑"; notifyEvent(); } }ConcreteMediator实现了Mediator定义的接口 并且内部维护三个对象 如果事件类型是Font,那么调用设置字体的事件
package mediator; public class ConcreteMediator extends Mediator { private Button button; private Display display; private Font font; ConcreteMediator() { button = new Button(this); display = new Display(this); font = new Font(this); } @Override void operation(Colleague event) { if (event instanceof Font) { setFontEvent(event); } } private void setFontEvent(Colleague event) { System.out.println(((Font) event).getFontName()); button.applyButton(); display.reDisplay(); } }测试代码
package mediator; public class Test { public static void main(String[] args){ Mediator mediator = new ConcreteMediator(); Font font = new Font(mediator); font.changeFont(); } }上面的示例中,以设置字体为例,当字体变化时,请求“项目经理”安排其他同事协助 “项目经理”operation(Colleague event) 发现是设置字体的事件后,调用对应的事件处理方法,也就是寻找其他同事进行协助 中介者模式将每个场景中对象之间的协作进行封装