案例引入
案例一
普通实现
在租房过程中,客户可能去找房东问房子是否可以租,但是房东可能要和家人进行一系列的沟通,最后还可能派出另一个家庭成员来和客户进行交流,整个沟通过程非常复杂、沟通线路繁多。如果是写成程序的模式,不同成员之间需要留好接口方便成员之间互相进行调用
【分析】
- 各个成员彼此联系,你中有我,我中有你,不利于松耦合
- 各个成员之间所传递的消息(参数)容易混乱
- 当系统增加一个新的成员时,或者执行流程改变时,代码的可维护性、扩展性都不理想
【改进】
- 使用中介者模式
中介者模式
客户只需要对接中介,其他成员互相之间不进行沟通,由中介来进行沟通。如 屋主—>爸爸
变成 屋主—>中介—>爸爸
。通过中介的联络,可以将成员之间的关联关系都搞定
案例二
现在很多家庭都配备了智能家居,包括各种设备,如闹钟、咖啡机、电视机、窗帘……
当主人想要看电视时,会让多个设备协同工作,来自动完成看电视的准备工作,比如流程为: 闹铃响起->咖啡机开始做咖啡->窗帘自动落下->电视机开始播放
介绍
基础介绍
- 在中介者模式中,团队组员之间不再互相沟通并私自做出决定,而是发生任何事情都向中介者报告,中介者站在整个团队的角度上对组员上报的事情做出决定。当中介者下达指示时,组员会立即执行
- 用一个中介对象来封装一系列的对象交互方法。中介者使各个对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互
- 中介者模式属于行为型模式
- 在MVC模式中,C(Controller控制器) 是M(Model模型) 和V(View视图)的中介者,在前后端交互时起到了中间人的作用
登场角色
尚硅谷
- Mediator 就是抽象中介者,定义了同事对象到中介者对象的接口
- Colleague 是抽象同事类
- ConcreteMediator 具体的中介者对象,实现抽象方法,他需要知道所有的具体同事类,即以HashMap管理所有同事类,并接受某个同事对象的消息,来协调其他同事完成相应的任务
- ConcreteColleague 具体的同事类,可能会有很多个,每个同事只知道自己的行为,而不了解其他同事类的行为(方法),他们都依赖中介者对象
《图解设计模式》
Mediator(仲裁者、中介者)
:负责定义与 Colleague 角色进行通信和做出决定的接口(API)ConcreteMediator(具体的仲裁者、中介者)
:负责实现 Mediator 角色的接口(API),负责实际上如何做出决定Colleague(同事)
:负责定义与Mediator角色进行通信的接口(API)ConcreteColleague(具体的同事)
:负责实现 Colleague 角色的接口(API)
案例实现
案例一:智能家庭
类图
【操作流程】
- 创建 ConcreteMediator 对象
- 创建各个同事类对象,比如: Alarm、CoffeeMachine、TV
- 在创建同事类对象的时候,就直接通过构造器,加入到 colleagueMap
- 同事类对象,可以调用sendMessage,最终会去调用ConcreteMediator的getMessage方法
- getMessage(核心方法)会根据接收到的同事对象发出的消息 来协调调用其它的同事对象来共同完成任务
实现
【抽象中介者类】
package com.atguigu.mediator.smarthouse; public abstract class Mediator { /** * 将给中介者对象,加入到集合中 * @param colleagueName * @param colleague */ public abstract void register(String colleagueName, Colleague colleague); /** * 接收消息, 具体的同事对象发出 * @param stateChange * @param colleagueName */ public abstract void getMessage(int stateChange, String colleagueName); public abstract void sendMessage(); }
【具体中介者类】
package com.atguigu.mediator.smarthouse; import java.util.HashMap; /** * 具体的中介者类 */ public class ConcreteMediator extends Mediator { /** * 集合,放入所有的同事对象 */ private HashMap<String, Colleague> colleagueMap; private HashMap<String, String> interMap; public ConcreteMediator() { colleagueMap = new HashMap<String, Colleague>(); interMap = new HashMap<String, String>(); } @Override public void register(String colleagueName, Colleague colleague) { colleagueMap.put(colleagueName, colleague); if (colleague instanceof Alarm) { interMap.put("Alarm", colleagueName); } else if (colleague instanceof CoffeeMachine) { interMap.put("CoffeeMachine", colleagueName); } else if (colleague instanceof TV) { interMap.put("TV", colleagueName); } else if (colleague instanceof Curtains) { interMap.put("Curtains", colleagueName); } } /** * 具体中介者的核心方法 * 1. 根据得到消息,完成对应任务 * 2. 中介者在这个方法,协调各个具体的同事对象,完成任务 * @param stateChange * @param colleagueName */ @Override public void getMessage(int stateChange, String colleagueName) { //处理闹钟发出的消息 if (colleagueMap.get(colleagueName) instanceof Alarm) { if (stateChange == 0) { // 老司机做咖啡 ((CoffeeMachine) (colleagueMap.get(interMap .get("CoffeeMachine")))).StartCoffee(); // 启动电视 ((TV) (colleagueMap.get(interMap.get("TV")))).StartTv(); } else if (stateChange == 1) { // 关掉电视 ((TV) (colleagueMap.get(interMap.get("TV")))).StopTv(); } } else if (colleagueMap.get(colleagueName) instanceof CoffeeMachine) { // 将窗帘升起来 ((Curtains) (colleagueMap.get(interMap.get("Curtains")))) .UpCurtains(); } else if (colleagueMap.get(colleagueName) instanceof TV) { //如果TV发现消息 } else if (colleagueMap.get(colleagueName) instanceof Curtains) { //如果是以窗帘发出的消息,这里处理... } } @Override public void sendMessage() { } }
【抽象同事类:Colleague】
package com.atguigu.mediator.smarthouse; /** * 抽象同事类 */ public abstract class Colleague { /** * 关联 Mediator */ private Mediator mediator; public String name; public Colleague(Mediator mediator, String name) { this.mediator = mediator; this.name = name; } public Mediator GetMediator() { return this.mediator; } public abstract void SendMessage(int stateChange); }
【具体同事类:闹钟】
package com.atguigu.mediator.smarthouse; /** * 具体的同事类 闹钟 */ public class Alarm extends Colleague { /** * 构造器 * @param mediator * @param name */ public Alarm(Mediator mediator, String name) { super(mediator, name); //在创建Alarm 同事对象时,将自己放入到ConcreteMediator 对象的集合中 mediator.register(name, this); } public void SendAlarm(int stateChange) { SendMessage(stateChange); } @Override public void SendMessage(int stateChange) { //调用的中介者对象的getMessage this.GetMediator().getMessage(stateChange, this.name); } }
【具体同事类:窗帘】
package com.atguigu.mediator.smarthouse; /** * 窗帘 */ public class Curtains extends Colleague { public Curtains(Mediator mediator, String name) { super(mediator, name); mediator.register(name, this); } @Override public void SendMessage(int stateChange) { this.GetMediator().getMessage(stateChange, this.name); } public void UpCurtains() { System.out.println("I am holding Up Curtains!"); } }
【具体同事类:电视】
package com.atguigu.mediator.smarthouse; public class TV extends Colleague { public TV(Mediator mediator, String name) { super(mediator, name); mediator.register(name, this); } @Override public void SendMessage(int stateChange) { this.GetMediator().getMessage(stateChange, this.name); } public void StartTv() { System.out.println("It's time to StartTv!"); } public void StopTv() { System.out.println("StopTv!"); } }
【主类】
package com.atguigu.mediator.smarthouse; public class ClientTest { public static void main(String[] args) { //创建一个中介者对象 Mediator mediator = new ConcreteMediator(); //创建Alarm并且加入到 ConcreteMediator 对象的HashMap Alarm alarm = new Alarm(mediator, "alarm"); //创建了CoffeeMachine对象,并且加入到 ConcreteMediator 对象的HashMap CoffeeMachine coffeeMachine = new CoffeeMachine(mediator, "coffeeMachine"); //创建 Curtains, 并且加入到 ConcreteMediator 对象的HashMap Curtains curtains = new Curtains(mediator, "curtains"); TV tV = new TV(mediator, "TV"); //让闹钟发出消息 alarm.SendAlarm(0); //做好咖啡 coffeeMachine.FinishCoffee(); alarm.SendAlarm(1); } }
【运行】
It's time to startcoffee! It's time to StartTv! After 5 minutes! Coffee is ok! I am holding Up Curtains! StopTv! Process finished with exit code 0
【分析】
- 程序拓展性较好:如果添加一个机器,只需要添加一个 ConcreteColleague 并修改 ConcreteMediator 的相关方法就行,客户端不用改变
案例二:登录页面逻辑实现
说明
需要实现一个系统登录表单功能,具体的处理逻辑如下:
- 如果选择作为游客访问,那么禁用用户名输入框和密码输入框,使用户无法输入
- 如果选择作为用户登录,那么启用用户名输入框和密码输入框,使用户可以输入
- 如果在用户名输入框中一个字符都没有输入,那么禁用密码输入框,使用户无法输入密码
- 如果在用户名输入框中输入了至少一个字符,那么启用密码输入框,使用户可以输入密码(当然,如果选择作为游客访问,那么密码框依然是禁用状态 )
- 只有当用户名输入框和密码输入框中都至少输入一个字符后,OK 按钮才处于启用状态,可以被按下
- 用户名输入框或密码输入框中一个字符都没有被输入的时候,禁用OK按钮,使其不可被按下(当然,如果选择作为游客访问,那么OK 按总是处于启用状态)
- Cancel按钮总是处于启用状态,任何时候都可以按下该按钮
类图
实现
中介者接口和组员接口的方法并非固定就是这些,当中介者和组员需要其他合作的话,就需要定义更多的方法
【中介者接口】
package com.atguigu.mediator.Sample; public interface Mediator { /** * 生成 Mediator 管理的组员 */ public abstract void createColleagues(); /** * 每个组员都会调用这个方法来向中介者汇报 */ public abstract void colleagueChanged(); }
【组员接口】
package com.atguigu.mediator.Sample; public interface Colleague { /** * 设置中介者,告诉组员中介者是谁 * * @param mediator */ public abstract void setMediator(Mediator mediator); /** * 告知组员中介者所下达的指令 * @param enabled 控制是否启用组员的功能 */ public abstract void setColleagueEnabled(boolean enabled); }
【组员:按钮】
package com.atguigu.mediator.Sample; import java.awt.*; public class ColleagueButton extends Button implements Colleague { private Mediator mediator; public ColleagueButton(String caption) { super(caption); } public void setMediator(Mediator mediator) { // 保存Mediator this.mediator = mediator; } public void setColleagueEnabled(boolean enabled) { // Mediator下达启用/禁用的指示 // setEnabled是java.awt.Button定义的方法,用来控制按钮组件是启用还是禁用,当设置为false时,按钮无法被按下 setEnabled(enabled); } }
【组员:文本输入框】
package com.atguigu.mediator.Sample; import java.awt.*; import java.awt.event.TextEvent; import java.awt.event.TextListener; /** * 实现 TextListener 接口来实现监听事件 */ public class ColleagueTextField extends TextField implements TextListener, Colleague { private Mediator mediator; public ColleagueTextField(String text, int columns) { // 构造函数 super(text, columns); } public void setMediator(Mediator mediator) { // 保存Mediator this.mediator = mediator; } public void setColleagueEnabled(boolean enabled) { // Mediator下达启用/禁用的指示 setEnabled(enabled); // 控件启用时,背景色变成白色;否则变为灰色 setBackground(enabled ? Color.white : Color.lightGray); } /** * TextListener 中定义的方法,监听文本内容的变化,并通知中介者 * @param e */ public void textValueChanged(TextEvent e) { // 当文字发生变化时通知Mediator mediator.colleagueChanged(); } }
【组员:单选按钮】
package com.atguigu.mediator.Sample; import java.awt.*; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; /** * 单选按钮 */ public class ColleagueCheckbox extends Checkbox implements ItemListener, Colleague { private Mediator mediator; public ColleagueCheckbox(String caption, CheckboxGroup group, boolean state) { // 构造函数 super(caption, group, state); } public void setMediator(Mediator mediator) { // 保存Mediator this.mediator = mediator; } public void setColleagueEnabled(boolean enabled) { // Mediator下达启用/禁用指示 setEnabled(enabled); } /** * 监听单选按钮的状态变化 * @param e */ public void itemStateChanged(ItemEvent e) { // 当状态发生变化时通知Mediator mediator.colleagueChanged(); } }
【具体中介者】
package com.atguigu.mediator.Sample; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; /** * 具体中介者 */ public class LoginFrame extends Frame implements ActionListener, Mediator { private ColleagueCheckbox checkGuest; private ColleagueCheckbox checkLogin; private ColleagueTextField textUser; private ColleagueTextField textPass; private ColleagueButton buttonOk; private ColleagueButton buttonCancel; /** * 构造函数 * 生成并配置各个Colleague后,显示对话框 * * @param title */ public LoginFrame(String title) { super(title); // 设置登录对话框的背景颜色 setBackground(Color.lightGray); // 使用布局管理器生成4×2窗格 setLayout(new GridLayout(4, 2)); // 生成各个 Colleague createColleagues(); // 配置 Colleague add(checkGuest); add(checkLogin); add(new Label("Username:")); add(textUser); add(new Label("Password:")); add(textPass); add(buttonOk); add(buttonCancel); // 设置初始的启用起用/禁用状态 colleagueChanged(); // 显示 pack(); show(); } /** * 生成登录对话框所需要的各个Colleague */ public void createColleagues() { // 生成 CheckboxGroup g = new CheckboxGroup(); checkGuest = new ColleagueCheckbox("Guest", g, true); checkLogin = new ColleagueCheckbox("Login", g, false); textUser = new ColleagueTextField("", 10); textPass = new ColleagueTextField("", 10); textPass.setEchoChar('*'); buttonOk = new ColleagueButton("OK"); buttonCancel = new ColleagueButton("Cancel"); // 设置Mediator checkGuest.setMediator(this); checkLogin.setMediator(this); textUser.setMediator(this); textPass.setMediator(this); buttonOk.setMediator(this); buttonCancel.setMediator(this); // 设置Listener checkGuest.addItemListener(checkGuest); checkLogin.addItemListener(checkLogin); textUser.addTextListener(textUser); textPass.addTextListener(textPass); buttonOk.addActionListener(this); buttonCancel.addActionListener(this); } /** * 控制各个成员的状态 * 接收来自于 Colleague 的通知,然后判断各 Colleague 的启用/禁用状态 * * 单选按钮的选中状态发生改变 或者 文本输入框的内容发生改变,都会调用这个方法 */ public void colleagueChanged() { // checkGuest.getState()获取游客模式的按钮是否处于选中状态 if (checkGuest.getState()) { // 游客模式 textUser.setColleagueEnabled(false); textPass.setColleagueEnabled(false); buttonOk.setColleagueEnabled(true); } else { // 登录模式 textUser.setColleagueEnabled(true); userpassChanged(); } } /** * 当textUser或是textPass文本输入框中的文字发生变化时 * 判断各Colleage的启用/禁用状态 */ private void userpassChanged() { if (textUser.getText().length() > 0) { textPass.setColleagueEnabled(true); if (textPass.getText().length() > 0) { buttonOk.setColleagueEnabled(true); } else { buttonOk.setColleagueEnabled(false); } } else { textPass.setColleagueEnabled(false); buttonOk.setColleagueEnabled(false); } } public void actionPerformed(ActionEvent e) { System.out.println(e.toString()); System.exit(0); } }
【主类】
package com.atguigu.mediator.Sample; public class Main { static public void main(String args[]) { new LoginFrame("Mediator Sample"); } }
【运行】
总结
【优点】
- 多个类相互耦合,会形成网状结构(通信路线很多),使用中介者模式将网状结构分离为星型结构进行解耦
- 减少类间依赖,峰低了耦合,符合迪米特原则
- 如果出现了Bug,比较容易定位Bug的位置
- ConcreteColleague容易复用(如果需要写一个新的对话框,那么按钮、文本输入框都可以很容易使用到新的对话框中)
【缺点】
- 中介者承担了较多的责任,一旦中介者出现了问题,整个系统就会受到影响
- 如果设计不当,中介者对象本身变得过于复杂,这点在实际使用时,要特别注意
- ConcreteMediator难以复用,因为其依赖于特定的应用程序
文章说明
- 本文章为本人学习尚硅谷的学习笔记,文章中大部分内容来源于尚硅谷视频(点击学习尚硅谷相关课程),也有部分内容来自于自己的思考,发布文章是想帮助其他学习的人更方便地整理自己的笔记或者直接通过文章学习相关知识,如有侵权请联系删除,最后对尚硅谷的优质课程表示感谢。
- 本人还同步阅读《图解设计模式》书籍(图解设计模式/(日)结城浩著;杨文轩译–北京:人民邮电出版社,2017.1),进而综合两者的内容,让知识点更加全面