设计模式学习笔记(二十):中介者模式
1 概述
1.1 引言
如果一个系统中对象之间的联系呈现为网状结构,存在大量的多对多联系,将导致系统非常复杂,比如,一个GUI窗口中,通过一个按钮更新了文本框,也更新了组合框,更新组合框的同时反过来需要更新文本框以及按钮。
这些对象既会影响别的对象,也会被别的对象所影响,这些对象称为同事对象,它们之间通过彼此相互作用实现系统的行为,几乎每一个对象都需要与其他对象发生相互作用,而这种相互作用表现为一个对象与另一个对象的直接耦合,这将导致一个过度耦合的系统。
中介者模式可以使对象之间的关系急剧减少,通过引入中介者对象,可以将系统的网状结构:
转化为以中介者为中心的星型结构:
在这个星型结构中,同事对象不再直接与其他的同事对象联系,通过中介者对象与另一个对象发生相互作用,中介者对象的存在保证了结构上的稳定,也就是说,系统的结构不会因为新对象的引入带来大量的修改工作。
如果一个系统中对象之间存在多对多的相互关系,可以将对象之间的一些交互行为从各个对象之间分离出来,并集中封装在一个中介者对象中,由中介者进行统一的协调,这样对象之间多对多的复杂关系就转变为相对简单的一对多关系,通过引入中介者来简化对象之间的复杂交互。
1.2 定义
中介者模式:用一个中介者对象来封装一系列的对象交互,中介者使各对象不需要显示地相互引用,从而使其松散耦合,而且可以独立地改变它们之间的交互。
中介者模式又叫调停者模式,是一种对象行为型模式。
1.3 结构图
1.4 角色
Mediator
(抽象中介者):定义了与各同事类之间进行通信的方法ConcreteMediator
(具体中介者):抽象中介者的子类,协调各个同事对象实现协作行为,维持对各个同事对象的引用Colleague
(抽象同事类):定义各个同事类的公有方法,并声明一些抽象方法来提供子类实现,同事维持一个抽象中介者的引用,子类可以通过该引用与中介者通信ConcreteColleague
(具体同事类):抽象同事的子类,每一个同事对象在需要和其他同事对象通信时,先与中介者通信,通过中介者来间接完成与其他同事类的通信,在具体同事类中实现了在抽象同事类中声明的抽象方法
2 典型实现
2.1 步骤
- 定义抽象同事类:声明具体同事类的方法,包含一个抽象中介者成员
- 定义具体同事类:继承/实现抽象同事类,添加一个数据更改以及数据更新的方法,其中数据更改方法供客户端调用,表示更改了具体同事类的某项数据,并且在其中调用抽象中介者的通知其他同事类更新的方法。数据更新方法供抽象中介者调用,当其他同事类修改数据时,抽象中介者通过该方法更新该同事类的数据
- 定义抽象中介者:包含一个抽象同事类的集合,存储具体同事类对象,同时声明一个通知其他同事类更新的方法
- 定义具体中介者:继承抽象中介者,根据需要实现业务方法,
其中第二步可能比较难理解,下面会有详细说明。
2.2 抽象同事类
abstract class Colleague
{
//抽象中介者引用
protected Mediator mediator;
public Colleague(Mediator mediator)
{
this.mediator = mediator;
}
//数据更新方法
public abstract void update();
//数据更改方法
public abstract void changed();
}
抽象同事类包含一个抽象中介者的引用,声明了数据更新方法以及数据更改方法。
至于为什么需要声明这两个方法,首先可以假设有两个同事类:两个文本框,其中一个文本框表示长度,用米作单位,另一个用千米做单位,当其中一个修改时,也就是changed()
被调用时,通过中介者调用另一个文本框的update()
使另一个文本框更新。
2.3 具体同事类
class ConcreteColleague1 extends Colleague
{
public ConcreteColleague1(Mediator mediator)
{
super(mediator);
}
@Override
public void update()
{
System.out.println("更新同事类1");
}
@Override
public void changed()
{
System.out.println("同事类1数据更改");
mediator.operation(this);
}
}
class ConcreteColleague2 extends Colleague
{
public ConcreteColleague2(Mediator mediator)
{
super(mediator);
}
@Override
public void update()
{
System.out.println("更新同事类2");
}
@Override
public void changed()
{
System.out.println("同事类2数据更改");
mediator.operation(this);
}
}
具体同事类中通过构造方法注入抽象中介者,维持一个抽象中介者的引用。另外在数据更改方法中,需要通过中介者通知其他同事类进行更新,也就是执行其他同事类的update()
方法。
2.4 抽象中介者
abstract class Mediator
{
protected ArrayList<Colleague> colleagues = new ArrayList<>();
public void add(Colleague colleague)
{
colleagues.add(colleague);
}
public abstract void operation(Colleague colleague);
}
抽象中介者使用集合存储所有的具体同事类,其中的operation
方法是通知其他同事类修改的方法,在此方法里面统一协调所有的同事类,从而避免各个同事类之间直接调用,降低耦合度。
2.5 具体中介者
class ConcreteMediator extends Mediator
{
@Override
public void operation(Colleague colleague)
{
if(colleague instanceof ConcreteColleague1)
colleagues.get(1).update();
else if(colleague instanceof ConcreteColleague2)
colleagues.get(0).update();
}
}
实现抽象中介者的业务方法,这里是传入一个抽象同事类参数,判断具体是哪一个同事类,按需要更新具体同事类即可。
2.6 客户端
public static void main(String[] args)
{
Mediator mediator = new ConcreteMediator();
Colleague colleague1 = new ConcreteColleague1(mediator);
Colleague colleague2 = new ConcreteColleague2(mediator);
mediator.add(colleague1);
mediator.add(colleague2);
colleague1.changed();
colleague2.changed();
}
客户端针对抽象中介者以及抽象同事类进行编程,先创建具体中介者,通过构造方法注入到各个具体同事类中,以便同事类调用对应中介者的方法,接着还需要将各个同事类添加到抽象中介者的集合成员中,以便抽象中介者对具体同事类进行统一的管理,最后调用具体同事类的方法。
输出如下:
3 实例
设计一个客户信息管理窗口,其中包含按钮,列表框,文本框,组合框组件,使用中介者模式进行设计。
设计如下:
- 抽象同事类:
Component
- 具体同事类:
Button
+ListBox
+TextBox
+ComboBox
- 抽象中介者:
Mediator
- 具体中介者:
ConcreteMediator
首先是抽象同事类,包含一个抽象中介者引用,以及数据更改和更新方法。
abstract class Component
{
protected Mediator mediator;
public Component(Mediator mediator)
{
this.mediator = mediator;
}
public abstract void update();
public abstract void changed();
}
接着是具体同事类:
class Button extends Component
{
public Button(Mediator mediator)
{
super(mediator);
}
@Override
public void update()
{
System.out.println("更新按钮");
}
@Override
public void changed()
{
System.out.println("按钮数据更改");
mediator.notifyAllComponent(this);
}
}
class ListBox extends Component
{
public ListBox(Mediator mediator)
{
super(mediator);
}
@Override
public void update()
{
System.out.println("更新列表框");
}
@Override
public void changed()
{
System.out.println("列表框数据更改");
mediator.notifyAllComponent(this);
}
}
class ComboBox extends Component
{
public ComboBox(Mediator mediator)
{
super(mediator);
}
@Override
public void update()
{
System.out.println("更新组合框");
}
@Override
public void changed()
{
System.out.println("组合框数据更改");
mediator.notifyAllComponent(this);
}
}
class TextBox extends Component
{
public TextBox(Mediator mediator)
{
super(mediator);
}
@Override
public void update()
{
System.out.println("更新文本框");
}
@Override
public void changed()
{
System.out.println("文本框数据更改");
mediator.notifyAllComponent(this);
}
}
一共四个具体同事类,每一个具体同事类代表一个UI组件,实现了其中的数据更新以及数据更改方法。
接着是抽象中介者类,为了更好地管理组件(具体同事对象)引入了一个组件管理类,以便在抽象中介者中可以统一使用put
添加组件:
enum ComponentName
{
BUTTON,LIST_BOX,TEXT_BOX,COMBO_BOX;
}
class AllComponent
{
private Map<ComponentName,Component> map = new HashMap<>();
public void put(Component component)
{
if(component instanceof Button)
map.put(ComponentName.BUTTON, component);
else if(component instanceof ListBox)
map.put(ComponentName.LIST_BOX, component);
else if(component instanceof ComboBox)
map.put(ComponentName.COMBO_BOX, component);
else if(component instanceof TextBox)
map.put(ComponentName.TEXT_BOX, component);
}
public Component get(ComponentName name)
{
return map.containsKey(name) ? map.get(name) : null;
}
}
abstract class Mediator
{
protected AllComponent allComponent = new AllComponent();
public void put(Component ... components)
{
for(Component component:components)
allComponent.put(component);
}
public abstract void notifyAllComponent(Component Component);
}
引入组件类的另一个原因是方便日后扩展组件,这样就不需要修改抽象中介者的代码,抽象中介者只需要维持一个组件管理类的引用。组件管理类使用一个Map
存储所有同事类对象,根据对应的具体同事类类型,使用枚举设置相应的键值。
另外抽象中介者中包含一个重要的notifyAllComponent
方法,该方法在某个组件的数据改变时调用,通知其他所有组件进行相应的更新。
最后是具体中介者类:
class ConcreteMediator extends Mediator
{
private Component button = null;
private Component listBox = null;
private Component comboBox = null;
private Component textBox = null;
@Override
public void notifyAllComponent(Component component)
{
if(button == null)
button = allComponent.get(ComponentName.BUTTON);
if(listBox == null)
listBox = allComponent.get(ComponentName.LIST_BOX);
if(comboBox == null)
comboBox = allComponent.get(ComponentName.COMBO_BOX);
if(textBox == null)
textBox = allComponent.get(ComponentName.TEXT_BOX);
if(component instanceof Button)
{
listBox.update();
textBox.update();
comboBox.update();
}
else if(component instanceof ListBox)
{
textBox.update();
comboBox.update();
}
else if(component instanceof TextBox)
{
button.update();
listBox.update();
}
else if(component instanceof ComboBox)
{
textBox.update();
listBox.update();
}
}
}
首先获取同事类对象,然后判断具体同事类的类型,按实际需要进行选择性更新同事类即可。
测试:
public static void main(String[] args)
{
Mediator mediator = new ConcreteMediator();
Component button = new Button(mediator);
Component listBox = new ListBox(mediator);
Component textBox = new TextBox(mediator);
Component comboBox = new ComboBox(mediator);
mediator.put(button,listBox,textBox,comboBox);
System.out.println("按钮更改事件:");
button.changed();
System.out.println();
System.out.println("列表框更改事件:");
listBox.changed();
System.out.println();
System.out.println("文本框更改事件:");
textBox.changed();
System.out.println();
System.out.println("组合框更改事件:");
comboBox.changed();
System.out.println();
}
客户端针对抽象中介者以及抽象同事类进行编程,创建完同事类后统一添加到抽象中介者中,最后更新对应同事类即可。
输出如下:
4 扩展中介者与同事类
在上面例子的基础上,现在系统需要增加一个Label
组件,也就是增加一个具体同事类,由于建立了抽象层,增加具体同事类很容易,对于如何在中介者中扩展有以下两种方法:
- 在原有具体中介者中增加一个
Label
成员并在对应方法添加else if
判断 - 继承原有具体中介者
4.1 方法1
首先增加一个Label
类:
class Label extends Component
{
public Label(Mediator mediator)
{
super(mediator);
}
@Override
public void update()
{
System.out.println("更新文本标签");
}
@Override
public void changed()
{
System.out.println("文本标签数据更改");
mediator.notifyAllComponent(this);
}
}
接着需要修改组件管理类:
enum ComponentName
{
BUTTON,LIST_BOX,TEXT_BOX,COMBO_BOX,LABEL;
}
class AllComponent
{
//...
public void put(Component component)
{
if(component instanceof Button)
map.put(ComponentName.BUTTON, component);
else if(component instanceof ListBox)
map.put(ComponentName.LIST_BOX, component);
else if(component instanceof ComboBox)
map.put(ComponentName.COMBO_BOX, component);
else if(component instanceof TextBox)
map.put(ComponentName.TEXT_BOX, component);
else if(component instanceof Label)
map.put(ComponentName.LABEL, component);
}
//...
}
添加一条else if
即可,最后修改具体中介者类:
class ConcreteMediator extends Mediator
{
private Component button = null;
private Component listBox = null;
private Component comboBox = null;
private Component textBox = null;
private Component label = null;
@Override
public void notifyAllComponent(Component component)
{
if(button == null)
button = allComponent.get(ComponentName.BUTTON);
if(listBox == null)
listBox = allComponent.get(ComponentName.LIST_BOX);
if(comboBox == null)
comboBox = allComponent.get(ComponentName.COMBO_BOX);
if(textBox == null)
textBox = allComponent.get(ComponentName.TEXT_BOX);
if(label == null)
label = allComponent.get(ComponentName.LABEL);
//...
}
}
其他无须修改,客户端一样可以针对抽象中介者以及抽象同事类进行编程。这样就顺利添加一个具体同事类了,代码需要改动的部分不多。
4.2 方法2
方法2也是需要像方法1一样新建一个Label
类以及修改组件管理类的代码,但是为了不修改具体中介者的代码,从具体中介者继承了一个新的具体中介者,代码如下:
class SubConcreteMediator extends ConcreteMediator
{
private Component label = null;
@Override
public void notifyAllComponent(Component component)
{
if(label == null)
label = allComponent.get(ComponentName.LABEL);
if(component instanceof Label)
{
Component textBox = allComponent.get(ComponentName.TEXT_BOX);
if(textBox != null)
textBox.update();
}
else
super.notifyAllComponent(component);
}
}
客户端需要将原来的具体中介者修改为新的具体中介者:
Mediator mediator = new SubConcreteMediator();
Component button = new Button(mediator);
Component listBox = new ListBox(mediator);
Component textBox = new TextBox(mediator);
Component comboBox = new ComboBox(mediator);
Component label = new Label(mediator);
mediator.put(button,listBox,textBox,comboBox,label);
label.changed();
继承是另一个达到目的的方法。事实上这两种方法本质上没有任何的区别,都是增加了一个Label
对象以及一条else if
,但是,由于方法2不需要修改原有的具体中介者类,符合开闭原则,因此推荐使用方法2,也就是对于新增具体同事类可以使用继承具体中介者类的方式进行处理。
5 主要优点
- 简化交互:中介者模式简化了对象之间的交互,它用中介者和同事的一对多交互代替了原来同事的多对多交互,一对多容易理解和扩展,将原本难以理解的网状结构转换为星型结构
- 解耦同事对象:中介者模式可将各个同事对象解耦,有利于各同事之间的松耦合,可以独立改变和复用每一个同事和中介者,增加新的中介者和新的同事类都很方便,更好地符合开闭原则
- 减少同事子类个数:中介者将原本分布于多个对象间的行为集中起来,改变这些行为只需要生成新的中介者子类即可,这使得各个同事类可以被重用,无须对同事类进行扩展
6 主要缺点
- 中介者类复杂:由于具体中介者中包含了大量的同事之间的交互细节,可能会导致具体中介者类变得非常复杂,使得系统难以维护
7 适用场景
- 系统对象之间存在复杂的引用关系,系统结构混乱且难以理解
- 一个对象由于引用了其他很多对象并且直接和这些对象通信,导致难以复用该对象
- 想通过一个中间类来封装多个类的行为,而又不想生成太多的子类