调停者模式简介
良好设计的应用由轻量级的对象组合而成,这些对象具有特定的职责,符合SOILD
的单一职责原则。然而,这些大量轻量级的对象给应用带来好处的同时也带来了对象之间交互通信的挑战。对象间需要通信来完成业务需求,当对象数量不断增加时,通信很快变的很难管理。另外,对象之间通信需要感知对方的存在,紧耦合在一起的对象违反了 SOLID
原则。调停者模式被证明可以解决这样的问题。
调停者模式定义了一个调停者(Mediator)对象来处理对象之间的交互,进而替代一组对象之间直接交互。下图简单展示有无调停者模式对象之间交互的方式。
如上图显示,利用 Mediator 对象使应用中对象无限知道其他对象的存在,Mediator 封装了一个通信协议让对象遵循。
在系统中,Mediator 对象封装了对象之间的所有交互逻辑。因此,如果一个对象根据新的交互规则被修改或者一个新的对象被创建,只要修改Mediator 对象,如果没有Mediator 对象,你需要知道交互中的所有对象。通过使用 Mediator 对象使你的代码封装性更好,不会牵一发动全身。
调停者模式中的参与者
想象一下一个战区,武装部队正在向敌人的领土进发。 武装部队可以包括士兵,坦克,掷弹兵和狙击部队。 采用的策略是,每当一个单位进攻时,其他单位就应停止进攻并掩盖。 为此,当前正在攻击的部队需要通知其他部队。
在编程世界中,你可以通过为每个部队创建一个来为需求建模。当一个类对象代表的部队将要开始发起攻击时,你可以实现对其他类对象的通知逻辑。现在,假设一个新的作战部队加入时,结果是所有参与交互的类都要修改,更糟糕的情况是,当作战策略修改,可以同时多个作战部队发起攻击,这时修改的代码会更多。通上面介绍描述的一样,我们可以通过调停者模式解决。在阵营中放一个指挥官来充当调停者,所有作战部队通过指挥官交流,指挥官把收到的通知按需发送给其他的部队。
开始对上面的作战部队的例子建模,把指挥官定义成 Commander
接口,也就是 Mediator,类 CommanderImpl
实现接口 Commander
, 接口定义了方法来发送消息。接口ArmedUnit
代表作战部队,实现类有 SoldierUnit
和 TankUnit
,实现类中引用了 Commander
。
调停者模式的代码实现
public interface Commander {
void registerArmedUnits(ArmedUnit soldierUnit, ArmedUnit tankUnit);
void setAttackStatus(boolean attackStatus);
boolean canAttack();
void startAttack(ArmedUnit armedUnit);
void ceaseAttack(ArmedUnit armedUnit);
}
public interface ArmedUnit {
void attack();
void stopAttack();
}
public class SoldierUnit implements ArmedUnit {
private final Commander commander;
public SoldierUnit(Commander commander) {
this.commander = commander;
}
@Override
public void attack() {
if (commander.canAttack()) {
System.out.println("SoldierUnit:Attacking...");
commander.setAttackStatus(false);
} else {
System.out.println("SoldierUnit:Cannot attack now,other units are attacking... ");
}
}
@Override
public void stopAttack() {
System.out.println("SoldierUnit:Stop Attacking...");
commander.setAttackStatus(true);
}
}
public class TankUnit implements ArmedUnit {
private final Commander commander;
public TankUnit(Commander commander) {
this.commander = commander;
}
@Override
public void attack() {
if (commander.canAttack()) {
System.out.println("TankUnit:Attacking...");
commander.setAttackStatus(false);
} else {
System.out.println("TankUnit:Cannot attack now,other units are attacking....");
}
}
@Override
public void stopAttack() {
System.out.println("TankUnit:Stop attacking...");
commander.setAttackStatus(true);
}
}
public class CommanderImpl implements Commander {
private ArmedUnit soldierUnit, tankUnit;
private boolean attackStatus = true;
@Override
public void registerArmedUnits(ArmedUnit soldierUnit, ArmedUnit tankUnit) {
this.soldierUnit = soldierUnit;
this.tankUnit = tankUnit;
}
@Override
public void setAttackStatus(boolean attackStatus) {
this.attackStatus = attackStatus;
}
@Override
public boolean canAttack() {
return this.attackStatus;
}
@Override
public void startAttack(ArmedUnit armedUnit) {
armedUnit.attack();
}
@Override
public void ceaseAttack(ArmedUnit armedUnit) {
armedUnit.stopAttack();
}
}
public class CommanderImplTest {
public static void main(String[] args) {
Commander commander = new CommanderImpl();
ArmedUnit soldierUnit = new SoldierUnit(commander);
ArmedUnit tankUnit = new TankUnit(commander);
commander.registerArmedUnits(soldierUnit, tankUnit);
commander.startAttack(soldierUnit);
commander.startAttack(tankUnit);
commander.ceaseAttack(soldierUnit);
commander.startAttack(tankUnit);
}
}
调停者模式在 Spring 框架中的应用
在spring 中,Spring MVC 是调停者模式的最近实践,用 DispatcherServlet 链接 Controllers 。
下图是Spring MVC 官方给出的架构图
从上图可以看出,DispatcherServlet 在web 请求和Controllers 中间扮演了 mediator 的角色。Controllers 之间感知不到对方的存在,controller 无需知道web 请求的存在, view templates 也无需知道controller 和 request 的存在。 Front Controller 的职责是选择合适的controller 和 view template 去响应请求。
因为使用了调停者模式,controller 之间互不影响,web 请求对象发生变化,也不会影响到下游的controller 和 view。
总结
调停者模式是一个被广泛使用的模式,主要职责是封装了对象之间的通信逻辑。这个模式的缺点需要需求的变化,mediator 对象会逐渐变复杂。但是,通过 SOLID 原则的单一职责原则,你可以把 mediator 对象按职责分划分为多个类来协助完成原有对象的职责。