每次乘坐高铁出行时,我都会像这样一个问题:这么多列车都可能通过这条轨道,会不会存在冲突的可能呢?同样的,飞机的起飞和降落时对于道路的选择也会有冲突的可能。这些情况都会造成可怕的后果, 而阻止这种情况发生的就是机场调度中心。飞机在起飞和降落前都会请求机场调度中心,由机场调度中心来负责协调飞机、地面道路、摆渡车辆等。因此,机场调度中心就属于机场的总指挥,会统一协调各单位的运行状态,使得飞机能够安全、顺利起飞和降落。
我们试想以下,如果没有机场调度中心的话,飞机在起飞时如何判断应该从哪个通道加速、起飞呢?飞机的机长需要确保乘客全部已经登机,还需要询问其他飞机是否在某车道内,或即将在某车道降落,还需要确保地面道路准备就绪,还需要确保天气的良好状况等等。可以看出,如果没有机场调度中心来负责这些起飞之外的其他情况的话,机长或机组人员会忙的不可开交,并且存在飞机道路冲突的风险。机场调度中心的存在使得飞机和其他影响单位不需要直接交互,是否可起飞和降落,完全由调度中心来调停和通知。类似这种降低各单位之间相互交互的耦合性的解决方案就是我们今天所要讲解的中介模式。
一、中介者模式概念
中介者模式,也称之为调停模式,其一般定义如下:
用一个中介对象来封装一系列对象之间的交互,中介对象使得各对象之间不需要显示相互作用,从而使得其耦合松散,而且可以独立改变他们之间的交互。
中介者模式就是为了解决各种对象之间繁杂的交互关系,将对象交互关系抽离出来,由中介对象负责这项功能。各对象之间更加关注自身的业务逻辑实现,而对象之间交互的业务逻辑有中介对象负责 。
一般情况下,对象之间的耦合是正常的,在迪米特法则那篇文章中,我们说到"在单一职责原则基础上吗,应该强耦合的朋友类之间所对应的业务逻辑也是强耦合的"。但是如果业务逻辑上,对象需要与很多对象之间互相交互,就可能会影响到单一职责原则,进而影响了业务稳定性。因此,在对象交互繁杂的情况下,可以通过中介者模式来拆分原则-将对象交互由中介对象负责 。
这里一定要区分"中介"和"代理"的区别。在代理模式的那篇文章中,我们提到房产中介其实也是代理的一种,这很容易导致很多同学对中介模式的理解偏差。代理是在原业务逻辑的基础上增加代理功能,而中介是为了协调不同业务对象之间的交互。在房产中介案例中,其提供给房东与购买者的中介信息(卖家与买家信息)就属于中介行为,而在实际购买过程中,中介人员协助购买者或卖家办理房产手续这部分就属于代理行为。
概念总结:
- 中介模式是为解决对象之间交互繁杂的问题。
- 中介模式使得业务对象更加关注其业务逻辑,业务对象交互由中介对象负责。
二、应用实践
为了更好的理解、应用中介者模式,本章通过具体的案例来实现下中介模式的应用场景。
案例背景就是关于上文提到的机场调度中心的场景。为了简化代码,突出重点,其中涉及的对象包括多架飞机(可调配对象),机场调度中心。机场调度中心需要负责协调多架飞机之间占用跑道的分配问题,飞机之间互相不存在耦合关系。
首先设计一个基本飞机类如下,这是一个抽象类。
java
/**
* 飞机基本抽象类
* (机场的飞机必须是可调度飞机,因此这里不可实例化)
*/
@Getter
public abstract class Airplane {
private String flightNumber; // 飞机航班编号
private String departure; // 起飞地
private String destination; // 目的地
private long departureTime; // 起飞时间
private long arrivalTime; // 降落时间
private long duration; // 航行时长
public void initAirplane() {
// 初始化航班属性...
}
// 飞机起飞
public abstract void takeOff();
// 飞机降落
public abstract void land();
}
为支持机场调度中心,设计可调度飞机类:
java
/**
* 可调配飞机类型
*/
public class DispatchableAirplane extends Airplane implements Dispatchable {
private AirportDispatcher dispatcherIns;
public DispatchableAirplane() {
initAirplane(); // 初始化基本航班属性
this.setDispatcher(AirportDispatcher.dispatcherIns); // 设置中介对象-机场调度中心
}
@Override
public void takeOff() {
// 申请跑道使用
Long permissionForRunway = dispatcherIns.assignRunwayNumber(this);
if(permissionForRunway == -1) {
System.out.println("暂无跑道,无法起飞");
return;
}
System.out.println("起飞喽~~,跑道编号:" + permissionForRunway);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
dispatcherIns.cancelRunway(permissionForRunway); // 起飞完成,告知调度中心以释放跑道
}
@Override
public void land() {
// 申请跑道使用
Long permissionForRunway = dispatcherIns.assignRunwayNumber(this);
if(permissionForRunway == -1) {
System.out.println("暂无跑道,无法降落");
return;
}
System.out.println("即将降落~~,跑道编号:" + permissionForRunway);
dispatcherIns.cancelRunway(permissionForRunway); // 降落完成,告知调度中心以释放跑道
}
@Override
public void setDispatcher(AirportDispatcher dispatcherIns) {
this.dispatcherIns = dispatcherIns;
}
}
可调度飞机类还实现了Dispatchable方法,用于表示其可被哪个调度中心调配:
java
public interface Dispatchable {
void setDispatcher(AirportDispatcher dispatcherIns);
}
其中AirportDispatcher就是飞机调度中心,调度中心应该是一个单例对象,因此其暂时不考虑抽象层。代码如下:
java
/**
* 机场调度中心类,注意这里应是单例
*/
public class AirportDispatcher {
public static final AirportDispatcher dispatcherIns = new AirportDispatcher();
// 最大跑道编号
private final Long maxRunwayNum;
// 所有可配跑道编号的占用情况
private final Map<Long, Dispatchable> runwayMaps = new ConcurrentHashMap<>();;
private AirportDispatcher() {
maxRunwayNum = 100L;
}
// 向外透出可分配跑道编号,-1表示暂无跑道
public Long assignRunwayNumber(Dispatchable reqDispatch) {
for (long no = 0; no < maxRunwayNum; no++) {
if(runwayMaps.containsKey(no)) {
continue;
}
Dispatchable dispatchable = runwayMaps.putIfAbsent(no, reqDispatch);
if(dispatchable == null) {
return no;
}
}
return -1L;
}
public void cancelRunway(Long RunwayNum) {
runwayMaps.remove(RunwayNum);
}
}
Client代码如下:
java
public class Client {
public static void main(String[] args) {
// 创建可调配飞机航班
DispatchableAirplane dispatchPlane1 = new DispatchableAirplane();
DispatchableAirplane dispatchPlane2 = new DispatchableAirplane();
new Thread(() -> {
try {
dispatchPlane1.takeOff();
Thread.sleep(dispatchPlane1.getDuration()); // 飞行中...
} catch (Exception e) {
// 不存在的异常
} finally {
dispatchPlane1.land();
}
}).start();
new Thread(() -> {
try {
dispatchPlane2.takeOff();
Thread.sleep(dispatchPlane2.getDuration()); // 飞行中...
} catch (Exception e) {
// 不存在的异常
} finally {
dispatchPlane2.land();
}
}).start();
}
}
从上面可以看出,AirportDispatcher主要负责的就是多架飞机(Dispatchable实例)对跑道的协调工作。Dispatchable实例之间不需要互相耦合交互。在扩展性方面,加入以上示例中机场调度中心还要负责地勤人员(GroundStaff )调度,那么GroundStaff也应该实现Dispatchable方法,因此这种设计下,几乎不需要改动原有代码,只需要实现GroundStaff的内部逻辑即可。
中介模式的优点:
- 进一步划分了类的职责,使得代码易读性、维护性更好
- 减少了类间的依赖,依赖或交互只存在于业务对象与中介对象之间
中介模式的缺点:
- 当对象关系种类越来越多时,会使得中介对象的逻辑也很复杂。【算缺点么,不使用中介模式岂不是更糟糕】