中介者模式
案例
张三最近非常的忙,因为他们公司接到了新的项目。虽然他只是一个程序员,然而在写代码的同时,更多的时间都在沟通项目的问题。比如:产品经理要和他对需求,UI设计师要和他沟通特殊的界面设计程序好不好实现,他自己还要写代码,每天都工作到很晚 (╯^╰)。就像是下面的程序一样:
1.定义程序员类:
public class Programmer {
private String name;
private UIDesigner designer;
private ProductManager productManager;
public Programmer(String name) {
this.name = name;
}
public void coding() {
System.out.println("程序员[" + this.name + "]开始写代码...");
}
public void communicateWithUI() {
System.out.println("程序员[" + this.name + "]与UI设计师[" + designer.getName() + "]沟通交流...");
}
public void communicateWithProductManager() {
System.out.println("程序员[" + this.name + "]与产品经理[" + productManager.getName() + "]沟通交流...");
}
public void setDesigner(UIDesigner designer) {
this.designer = designer;
}
public void setProductManager(ProductManager productManager) {
this.productManager = productManager;
}
public String getName() {
return name;
}
}
2.UI设计师类:
public class UIDesigner {
private String name;
private Programmer programmer;
private ProductManager productManager;
public UIDesigner(String name) {
this.name = name;
}
public void design() {
System.out.println("UI设计师[" + this.name + "]开始设计UI界面...");
}
public void communicateWithProductManager() {
System.out.println("UI设计师[" + this.name + "]与产品经理[" + productManager.getName() + "]沟通交流...");
}
public void communicateWithProgrammer() {
System.out.println("UI设计师[" + this.name + "]与程序员[" + programmer.getName() + "]沟通交流...");
}
public void setProgrammer(Programmer programmer) {
this.programmer = programmer;
}
public void setProductManager(ProductManager productManager) {
this.productManager = productManager;
}
public String getName() {
return name;
}
}
3.产品经理类:
public class ProductManager {
private String name;
private Programmer programmer;
private UIDesigner designer;
public ProductManager(String name) {
this.name = name;
}
public void designPrototype() {
System.out.println("产品经理[" + this.name + "]开始设计原型界面...");
}
public void communicateWithProgrammer() {
System.out.println("产品经理[" + this.name + "]与程序员[" + programmer.getName() + "]沟通交流...");
}
public void communicateWithUI() {
System.out.println("产品经理[" + this.name + "]与UI设计师[" + designer.getName() + "]沟通交流...");
}
public void setProgrammer(Programmer programmer) {
this.programmer = programmer;
}
public void setDesigner(UIDesigner designer) {
this.designer = designer;
}
public String getName() {
return name;
}
}
4.模拟:
public class Main {
public static void main(String[] args) {
Programmer programmer = new Programmer("张三");
UIDesigner designer = new UIDesigner("李四");
ProductManager productManager = new ProductManager("王五");
// 张三、李四和王五要相互交流,设置具体人员
programmer.setDesigner(designer);
programmer.setProductManager(productManager);
designer.setProgrammer(programmer);
designer.setProductManager(productManager);
productManager.setDesigner(designer);
productManager.setProgrammer(programmer);
productManager.designPrototype();
designer.design();
programmer.coding();
productManager.communicateWithUI();
productManager.communicateWithProgrammer();
designer.communicateWithProductManager();
designer.communicateWithProgrammer();
programmer.communicateWithProductManager();
programmer.communicateWithUI();
}
}
5.模拟结果:
产品经理[王五]开始设计原型界面...
UI设计师[李四]开始设计UI界面...
程序员[张三]开始写代码...
产品经理[王五]与UI设计师[李四]沟通交流...
产品经理[王五]与程序员[张三]沟通交流...
UI设计师[李四]与产品经理[王五]沟通交流...
UI设计师[李四]与程序员[张三]沟通交流...
程序员[张三]与产品经理[王五]沟通交流...
程序员[张三]与UI设计师[李四]沟通交流...
在这一过程中张三要同时与李四和王五沟通交流,如果李四有问题要与张三交流,张三也可能有问题要与王五交流。这样一天的大部分时间他们都在与其他人的沟通中,最后的结果就是大家一起加班了。从代码的角度来看就是程序员类维护着 UI 设计师类和产品经理类的引用,UI 设计师类维护着程序员类和产品经理类的引用,同样产品经理类中也有着其他两个类的引用,使得各个类在协调交互时产生大量直接的多对多的关系,导致对象之间过度耦合。类似下面的图形:
像这样对象与对象之间发生直接相互引用的情况可以通过引入一个中介者类的方式来解决这一问题,称之为中介者模式。
模式介绍
中介者模式(Mediator Pattern):用一个中介对象(中介者)来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。中介者模式又称为调停者模式,它是一种对象行为型模式。
在中介者模式中通过引入中介者类来协调其他类之间相互调用的问题,为了使系统有更好的灵活性和扩展性,通常还会先定义抽象中介者类,根据实际情况如果只存在一种具体的中介者类则不需要创建抽象中介者类。
角色构成
- Mediator(抽象中介者):它定义一个接口,该接口用于与各同事对象之间进行通信。
- ConcreteMediator(具体中介者):它是抽象中介者的子类,通过协调各个同事对象来实现协作行为,它维持了对各个同事对象的引用。
- Colleague(抽象同事类):它定义各个同事类公有的方法,并声明了一些抽象方法来供子类实现,同时它维持了一个对抽象中介者类的引用,其子类可以通过该引用来与中介者通信。
- ConcreteColleague(具体同事类):它是抽象同事类的子类;每一个同事对象在需要和其他同事对象通信时,先与中介者通信,通过中介者来间接完成与其他同事类的通信;在具体同事类中实现了在抽象同事类中声明的抽象方法。
UML类图
代码改造
中介者模式的思想就是通过引入中介者类来协调其他类之间相互耦合的问题,所以下面对案例代码进行改造:
1.首相定义一个抽象中介者类:
/**
* 抽象中介者类
*/
public abstract class Mediator {
private String name;
// 存放同事类对象
protected List workerList = new ArrayList<>();
public Mediator(String name) {
this.name = name;
}
// 添加同事类
public void addWorker(Worker worker) {
workerList.add(worker);
}
// 与同事类沟通交流
abstract void communicateWithWorker(String position);
public String getName() {
return name;
}
}
2.具体中介者类项目经理类:
/**
* 项目经理类:具体中介者类角色
*/
public class ProjectManager extends Mediator {
public ProjectManager(String name) {
super(name);
}
// 实现与其他同事类沟通的逻辑:这里是根据想沟通的具体职位来协调
@Override
public void communicateWithWorker(String position) {
for (Worker worker : workerList) {
if (worker.getPosition().equals(position)) {
System.out.println("项目经理[" + this.getName() + "]与" + worker.getPosition() + "[" + worker.getName() + "]沟通交流...");
}
}
}
}
3.抽象同事角色员工类:
/**
* 员工类:抽象同事类角色
*/
public abstract class Staff {
private String name;
private ProjectManager projectManager;
public Staff(String name) {
this.name = name;
}
// 有事就只与项目经理沟通,让项目经理协调具体事宜
public void communicateWithProjectManager(String position) {
System.out.println(this.getPosition() + "[" + this.name + "]与项目经理[" + projectManager.getName() + "]沟通交流...");
projectManager.communicateWithStaff(position);
}
// 抽象方法,便于统一调用
public abstract void work();
// 这里设计了返回职位的抽象方法,使得具体中介者根据这一返回值做出相应协调逻辑
public abstract String getPosition();
public void setProjectManager(ProjectManager projectManager) {
this.projectManager = projectManager;
projectManager.addStaff(this);
}
public String getName() {
return name;
}
}
4.三个员工继承类:
程序员类:
/**
* 程序员类:具体同事类角色
*/
public class Programmer extends Staff {
public Programmer(String name) {
super(name);
}
@Override
public void work() {
System.out.println("程序员[" + this.getName() + "]开始写代码...");
}
@Override
public String getPosition() {
return "程序员";
}
}
UI设计师类:
/**
* UI设计师类:具体同事类角色
*/
public class UIDesigner extends Staff {
public UIDesigner(String name) {
super(name);
}
@Override
public void work() {
System.out.println("UI设计师[" + this.getName() + "]开始设计UI界面...");
}
@Override
public String getPosition() {
return "UI设计师";
}
}
产品经理类
/**
* 产品经理类:具体同事类角色
*/
public class ProductManager extends Staff {
public ProductManager(String name) {
super(name);
}
@Override
public void work() {
System.out.println("产品经理[" + this.getName() + "]开始设计原型界面...");
}
@Override
public String getPosition() {
return "产品经理";
}
}
5.测试类:
public class Main {
public static void main(String[] args) {
Programmer programmer = new Programmer("张三");
UIDesigner designer = new UIDesigner("李四");
ProductManager productManager = new ProductManager("王五");
ProjectManager projectManager = new ProjectManager("赵六");
programmer.setProjectManager(projectManager);
designer.setProjectManager(projectManager);
productManager.setProjectManager(projectManager);
productManager.work();
designer.work();
programmer.work();
productManager.communicateWithProjectManager("UI设计师");
designer.communicateWithProjectManager("程序员");
programmer.communicateWithProjectManager("产品经理");
}
}
6.模拟结果:
产品经理[王五]开始设计原型界面...
UI设计师[李四]开始设计UI界面...
程序员[张三]开始写代码...
产品经理[王五]与项目经理[赵六]沟通交流...
项目经理[赵六]与UI设计师[李四]沟通交流...
UI设计师[李四]与项目经理[赵六]沟通交流...
项目经理[赵六]与程序员[张三]沟通交流...
程序员[张三]与项目经理[赵六]沟通交流...
项目经理[赵六]与产品经理[王五]沟通交流...
代码经过改造后,我们通过中介者(项目经理类)提供的中转作用,使得各个同事对象之间就不再需要显式引用其他同事,当需要和其他同事进行通信时,可通过中介者来实现间接调用。类似于下面这一关系图:
虽然这样使得各个对象间的耦合减少了,但同时使得中介者类的复杂度提高了,这也算是中介者模式的重要缺点了。
模式应用
下面介绍一下中介者模式在java.util.Timer
中的应用,下面演示一句话每隔一秒打印,另一句话每隔两秒打印:
1.首先定义两个任务类:
任务类A
public class TimerTaskA extends TimerTask {
@Override
public void run() {
System.out.println("TimerTaskA run");
}
}
任务类B
public class TimerTaskB extends TimerTask {
@Override
public void run() {
System.out.println("TimerTaskB run");
}
}
2.测试类:
public class Main {
public static void main(String[] args) {
Timer timer = new Timer("TimerTest");
// 每隔一秒执行
timer.schedule(new TimerTaskA(), 0, 1000);
// 每隔两秒执行
timer.schedule(new TimerTaskB(), 0, 2000);
}
}
3.测试结果:
TimerTaskA run
TimerTaskB run
TimerTaskA run
TimerTaskB run
TimerTaskA run
TimerTaskA run
TimerTaskB run
TimerTaskA run
TimerTaskA run
TimerTaskB run
...
从代码中可以分析出,TimerTask
看作是中介者模式中的抽象同事类,那么新建的两个任务类TimerTaskA
和TimerTaskB
就是两个具体同事类,Timer
则是中介者类了。这里Timer
中介者类的作用就是协调两个任务类的调用时间,下面看一下Timer
类源码:
1.首先是添加两个任务类的方法schedule
:
public void schedule(TimerTask task, long delay, long period) {
if (delay < 0)
throw new IllegalArgumentException("Negative delay.");
if (period <= 0)
throw new IllegalArgumentException("Non-positive period.");
sched(task, System.currentTimeMillis()+delay, -period);
}
2.在最后它调用了私有的sched()
方法:
private void sched(TimerTask task, long time, long period) {
if (time < 0)
throw new IllegalArgumentException("Illegal execution time.");
// Constrain value of period sufficiently to prevent numeric
// overflow while still being effectively infinitely large.
if (Math.abs(period) > (Long.MAX_VALUE >> 1))
period >>= 1;
synchronized(queue) {
if (!thread.newTasksMayBeScheduled)
throw new IllegalStateException("Timer already cancelled.");
synchronized(task.lock) {
if (task.state != TimerTask.VIRGIN)
throw new IllegalStateException(
"Task already scheduled or cancelled");
task.nextExecutionTime = time;
task.period = period;
task.state = TimerTask.SCHEDULED;
}
queue.add(task);
if (queue.getMin() == task)
queue.notify();
}
}
这个方法内将任务类放入到了私有属性queue
中,它是存放任务的任务队列类TaskQueue
的实例对象,TaskQueue
是Timerd
的内部类,在它的源码介绍中它提到了它的内部使用了平衡二叉堆来存放具体任务类,这个二叉堆根据任务类中的下次执行时间进行排列的:
/**
* This class represents a timer task queue: a priority queue of TimerTasks,
* ordered on nextExecutionTime. Each Timer object has one of these, which it
* shares with its TimerThread. Internally this class uses a heap, which
* offers log(n) performance for the add, removeMin and rescheduleMin
* operations, and constant time performance for the getMin operation.
*/
class TaskQueue {
/**
* Priority queue represented as a balanced binary heap: the two children
* of queue[n] are queue[2*n] and queue[2*n+1]. The priority queue is
* ordered on the nextExecutionTime field: The TimerTask with the lowest
* nextExecutionTime is in queue[1] (assuming the queue is nonempty). For
* each node n in the heap, and each descendant of n, d,
* n.nextExecutionTime <= d.nextExecutionTime.
*/
private TimerTask[] queue = new TimerTask[128];
//...
}
3.任务类实际运行的协调代码是在内部类TimerThread
中,它继承了了Thread
,并且在初始化Timer
实例时,即调用Timer timer = new Timer("TimerTest");
时TimerThread
线程就启动了:
// Timer 构造函数
public Timer(String name) {
thread.setName(name);
thread.start();
}
4.它是线程Thread
继承类,所以他就会调用run()
方法:
public void run() {
try {
mainLoop();
} finally {
// Someone killed this Thread, behave as if Timer cancelled
synchronized(queue) {
newTasksMayBeScheduled = false;
queue.clear(); // Eliminate obsolete references
}
}
}
5.可以看到它在其中调用了mainLoop()
方法:
private void mainLoop() {
while (true) {
try {
TimerTask task;
boolean taskFired;
synchronized(queue) {
// Wait for queue to become non-empty
while (queue.isEmpty() && newTasksMayBeScheduled)
queue.wait();
if (queue.isEmpty())
break; // Queue is empty and will forever remain; die
// Queue nonempty; look at first evt and do the right thing
long currentTime, executionTime;
// 在队列中,即二叉堆 queue 中获取数组中第二个任务
/**
* getMin() 方法源码
* TimerTask getMin() {
* return queue[1];
* }
*/
task = queue.getMin();
synchronized(task.lock) {
// 如果任务取消,从队列 queue 中移除任务
if (task.state == TimerTask.CANCELLED) {
queue.removeMin();
continue; // No action required, poll queue again
}
// 获取当前时间
currentTime = System.currentTimeMillis();
// 获取任务的下次执行时间
executionTime = task.nextExecutionTime;
// 判断下次执行时间是否小于等于当前时间,并赋值给 taskFired
if (taskFired = (executionTime<=currentTime)) {
// 判断重复时间是否为零
if (task.period == 0) { // Non-repeating, remove
// 如果是则从任务队列中移除
queue.removeMin();
task.state = TimerTask.EXECUTED;
} else { // Repeating task, reschedule
// 如果不是则用间隔时间计算下次执行时间重新放入到二叉堆中
// 即数组中第二个任务
queue.rescheduleMin(
task.period<0 ? currentTime - task.period
: executionTime + task.period);
}
}
}
// 如果不到任务执行时间
if (!taskFired) // Task hasn't yet fired; wait
// 任务等待执行时间与当前时间的差值之后再执行
queue.wait(executionTime - currentTime);
}
// 如果到了任务执行时间
if (taskFired) // Task fired; run it, holding no locks
// 调用任务类的方法,这里就是 TimerTaskA 或 TimerTaskB 中的逻辑代码
task.run();
} catch(InterruptedException e) {
}
}
}
可以看到Timer
类中定义的类或者方法,就是为了使得TimerTask
类实例间的协调执行。协调逻辑的核心方法就是上面分析的类TimerThread
中的mainLoop()
方法。方法内部判断了一系列的计算逻辑最使得具体任务类成功执行。
总结
1.主要优点
- 中介者模式简化了对象之间的交互,它用中介者和同事的一对多交互代替了原来同事之间的多对多交互,一对多关系更容易理解、维护和扩展,将原本难以理解的网状结构转换成相对简单的星型结构。
- 中介者模式可将各同事对象解耦。中介者有利于各同事之间的松耦合,我们可以独立的改变和复用每一个同事和中介者,增加新的中介者和新的同事类都比较方便,更好地符合“开闭原则”。
- 可以减少子类生成,中介者将原本分布于多个对象间的行为集中在一起,改变这些行为只需生成新的中介者子类即可,这使各个同事类可被重用,无须对同事类进行扩展。
2.主要缺点
- 在具体中介者类中包含了大量同事之间的交互细节,可能会导致具体中介者类非常复杂,使得系统难以维护。
3.适用场景
- 系统中对象之间存在复杂的引用关系,系统结构混乱且难以理解。
- 一个对象由于引用了其他很多对象并且直接和这些对象通信,导致难以复用该对象。
- 想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。可以通过引入中介者类来实现,在中介者中定义对象交互的公共行为,如果需要改变行为则可以增加新的具体中介者类。
参考资料
- 大话设计模式
- 设计模式Java版本-刘伟
- 设计模式 | 中介者模式及典型应用
本篇文章github代码地址:https://github.com/Phoegel/design-pattern/tree/main/mediator
转载请说明出处,本篇博客地址:https://www.jianshu.com/p/1af8378ceeb7