Java与模式-中介者模式

一个系统或者模块的类和类之间的交互如下图所示:


从图中可以看出,每一个类几乎都与另外所有的类有交互,如果类A发生了修改,那么类B,D,E,G,I等类也需要随着修改;反过来,如果其他几个类有变动,那么类A也需要修改。这个系统中,类与类之间耦合度太高,如果是一个真正的系统,那么会有比这多的多的类,如果类与类这间也是这样的交互关系,那就真是牵一发而动全身了,这个系统几乎就是无法维护,无法修改,无法扩展的。这是一个过度耦合的系统,在软件开发中,我们一定要避免这种情况的发生。可是该如何避免呢?

上图中的繁杂在于多个对象之间都有交互,从而导致耦合过密,不利于对象的修改和扩展。如果我们再引进一个对象扮演中间人的角色,所有的对象都与中间人通信,所有的交互都由中间人来处理。一旦某个对象作出了修改,它只需要通知中间人,由中间人来协调其他对象,反过来,其他对象的修改也不会影响到这个对象。系统的类图由上面的网状结构变成下面的星状结构:


对象之间的关系就很简单,它们只与中间人耦合,而不需要知道其他对象的状态。把上面的关系再提炼一下,就得到一种设计模式。

中介者(Mediator)模式

中介者模式的定义:用一个中介对象来封装一些列的对象交互,中介者使得各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。中介者模式解决问题的思路很简单,就是通过引入一个中介对象,让其他对象只与中介对象交互,而中介对象知道如何和其他所有对象的交互,这样对象之间的交互关系就没有了,从而实现了对象之间的解耦。由此,我们也可以看出一个问题,那就是中介对象控制着整个系统的逻辑,它会过于复杂,这是一个缺点。中介者模式的本质是封装交互

(1)对象在自身状态发生改变时报告给中介对象;

(2)中介对象控制着整个系统的逻辑,它知道如何与所有对象交互;

(3)对象需要对中介对象发出的请求作出回应。

中介者模式中的角色:

Mediator:中介者接口,定义各个同事之间交互所需要的方法;

ConcreteMediator:具体的中介者,它需要了解并维护各个同事对象,并负责具体的协调各同事对象的交互关系;

Colleague:所有同事对象的父类,一般实现成抽象类,主要负责约束同事对象的类型,并负责实现一些公共功能;

ConcreteMediator:具体的同事类,实现自己的业务,当需要与其他同事对象通信时,就与持有的中介者通信,中介者会负责与其他同事的交互。在标准的中介者模式中,将使用中介者来交互的那些对象叫做同事类,它们继承自相同的父类,所以叫做同事。正是由于它们之间的交互很复杂,所以才产生了把这些交互关系分离出去,让中介者来处理。

还是用些代码来清晰的展示一下中介者模式。以电脑来看电影为例子,首先光驱从光盘中读取数据,然后通知CPU将数据分离成音频和视频,CPU处理完毕后再分别将数据传送给声卡和显卡进行播放。从上面的描述的中发现,光驱盒CPU是耦合的,CPU又和声卡显卡是耦合的,怎么解耦的呢?如果使用中介者模式,通过引入主板作为中介者,所有的对象都与主板交互,那么播放电影的流程就变成了这样:

(1)光驱从光盘读取到数据,通知主板,数据准备好了;

(2)主板收到光驱的请求后,将原始数据传给CPU,让它将数据分离成音频和视频;

(3)CPU将数据分离后,通知主板,数据分离完毕;

(4)主板收到CPU通知后,分别将音频和视频传给声卡和显卡;

(5)声卡和显卡同时播放。

这样一个过程中,所有的类只与主板耦合,而不与其他类保持关系,做到了解耦,而且过程很清晰。实际上计算机硬件就是这样通信的,只不过更复杂一些,所以这些东西都是相通的,重要的是思想。下面给出示意性的代码,首先是同事类:

/**
 * 同事对象的父类,一般实现成抽象类,用于约束同事对象的类型
 * 同时实现一些功能公共方法,例如持有中介者对象
 */
public abstract class Colleague {
	//所有的同事对象都需要持有中介对象
	private Mediator mediator;
	public Colleague(Mediator mediator) {
		this.mediator = mediator;
	}
	
	public Mediator getMediator() {
		return mediator;
	}
}

/*光驱类,负责从光盘中读取数据*/
class CDDriver extends Colleague {
	//从光盘读取的原始数据
	private String originData;
	public CDDriver(Mediator mediator) {
		super(mediator);
	}
	
	public String getOriginData() {
		return originData;
	}
	
	/*读取光盘数据,一旦读取到数据,就要通知中介者对象数据已经准备好了*/
	public void readCD() {
		originData = "巴萨罗那,简称巴萨";
		//通知中介对象,自己的状态发生了改变
		getMediator().changed(this);
	}
}

/*CPU类,负责将原始数据分离成音频和视频*/
class CPU extends Colleague {
	//声音数据
	private String soundData;
	//视频数据
	private String videoData;
	
	public CPU(Mediator mediator) {
		super(mediator);
	}
	
	public String getSoundData() {
		return soundData;
	}
	public String getVideoData() {
		return videoData;
	}

	/*将数据分离,同时通知中介者对象,数据已经分离*/
	public void sperateData(String originData) {
		this.soundData = originData.split(",")[1];
		this.videoData = originData.split(",")[0];
		
		//通知中介对象,自己的状态发生了改变
		getMediator().changed(this);
	}
}

/*显卡类,播放视频*/
class VideoCard extends Colleague {
	public VideoCard(Mediator mediator) {
		super(mediator);
	}
	
	public void showVideo(String videoData) {
		System.out.println("正在观看:" + videoData);
	}
}
/*声卡类,播放声音*/
class SoundCard extends Colleague {
	public SoundCard(Mediator mediator) {
		super(mediator);
	}
	
	public void showSound(String soundData) {
		System.out.println("解说:" + soundData);
	}
}
中介者:
package com.javaandpatterns.mediator;
/*中介者接口*/
public interface  Mediator {
	//同事对象自身状态改变时,通过这个方法通知中介者对象
	void changed(Colleague obj);
	
	//中介者对象需要知道所有同事对象
	public void setCDDriver(CDDriver instance);
	public void setCPU(CPU instance);
	public void setVideoCard(VideoCard instance);
	public void setSoundCard(SoundCard instance);
}

class MainBoard implements Mediator {
	private CDDriver cd;
	private CPU cpu;
	private VideoCard vc;
	private SoundCard sc;

	public void setCDDriver(CDDriver instance) {
		this.cd = instance;
	}
	public void setCPU(CPU instance) {
		this.cpu = instance;
	}
	public void setVideoCard(VideoCard instance) {
		this.vc = instance;
	}
	public void setSoundCard(SoundCard instance) {
		this.sc = instance;
	}
	
	/**
	 * 当同时对象自身状态发生改变时,调用此方法通知中介者对象
	 * 中介者对象在进行逻辑控制,与其他同对象交互
	 */
	public void changed(Colleague obj) {
		//如果是光驱类,需要通知CPU去分离数据
		if(obj instanceof CDDriver) {
			String originData = ((CDDriver) obj).getOriginData();
			this.cpu.sperateData(originData);
		}else if(obj instanceof CPU){//如果是CPU类,需要通知声卡和显卡去播放
			String videoData = ((CPU) obj).getVideoData();
			String soundData = ((CPU) obj).getSoundData();
			this.vc.showVideo(videoData);
			this.sc.showSound(soundData);
		}
	}	
}
运行的一个例子:
package com.javaandpatterns.mediator;

public class Client {

	public static void main(String[] args) {
		Mediator mediator = new MainBoard();
		CDDriver cd = new CDDriver(mediator);
		CPU cpu = new CPU(mediator);
		VideoCard vc = new VideoCard(mediator);
		SoundCard sc = new SoundCard(mediator);
		
		mediator.setCDDriver(cd);
		mediator.setCPU(cpu);
		mediator.setSoundCard(sc);
		mediator.setVideoCard(vc);
		
		//光驱读数据,通知中介者,中介者通知CPU去分离数据,CPU分离数据完成,通知中介者,中介者通知声卡和显卡播放
		cd.readCD();
	}
}
讲解

中介者的功能就是封装对象之间的交互,它集中控制系统的交互。

需不需要Mediator接口?接口是用来封装隔离的,Mediator接口用来封装中介者对象,让同事对象与中介者对象隔离开来,让同事对象面向中介者接口编程。了解这点之后,那就需要看系统的实际需求。如果系统中需要不止一个中介者实现,或者预计中会有扩展的需求,那么就需要Mediator接口;如果系统只需要一个中介者实现,预计未来也不会有扩展,那么就不需要Mediator接口。

同事对象如何与中介者对象通信?可以像上面实例中那样,定义一个通用的changed方法,并把同事对象作为参数传入,这样在中介者对象里就可以获取同事对象的实例,进而做其他操作。更具体的,中介者对象可以为具体的同事对象实现具体的方法,这样便于进行简洁的逻辑控制。在上面的实例中,中介者对象可以为CDDriver类专门实现一个CDChanged方法,这样交互的话就更清晰,实现起来也不容易出错:

public void CDChanged(String originData) {
		this.cpu.sperateData(originData);
}
模式的优点:

(1)松散耦合,消除了同事对象之间的耦合;

(2)集中控制交互,同事对象之间的交互都被封装到中介者对象中,如果交互发生变化,只需要修改中介者对象就可以了;

(3)将多对多的关系变成一对多,让对象关系更加清晰,容易实现,其实还是解耦。

缺点:多度集中化,如果同事对象之间的交互比较多且比较复杂,中介者在封装这些交互时也会变得更加复杂,而且难于管理和维护。

使用场景

中介者模式是被滥用的最多的设计模式之一,在使用中介者模式之前,一定要正确分析系统的需求,同时正确理解中介者模式,然后再决定是否使用此模式。

(1)如果一组对象之间交互复杂,导致相互依赖,结构混乱,可以采用中介者模式,使各个对象松散耦合,结构清晰;

(2)如果一个类引用很多类,并直接跟这些类交互,导致难以复用该类,可以采用中介者模式,把这个类跟其他类的交互封装到中介者里面,这样该类就只需跟中介者交互了。

广义中介者模式

仔细研究上面的中介者模式会发现存在几个问题:

1.必须为同事类定义公共的父类。在实际的开发中,很多相互交互的对象本身没有公共的父类,强行加上一个父类会破坏这个类的结构,引起诸多不合理的实现,这是很没必要的。如果强行加上公共父类,继承它也得不到任何好处。如果有内在逻辑关系,当然可以定义公共的父类。

2.上面定义了中介者接口。在实际开发中,很常见的是不需要中介者接口,中介者对象也不需要有很多实例。中介者对象是用来封装和处理同事对象关系的,它一般是没有状态需要维护的,因此中介者对象可以实现成单例。

3.同事对象需要持有中介者对象吗?同事对象肯定需要知道中介者对象,但是否有必要把中介者对象作为属性并通过构造方法传入这么强的依赖关系?其实可以通过简单的方式通知中介者对象,比如中介者对象实现为单例,同事对象可以直接调用。

4.中介者对象是否有必要持有同事对象?和上面一样,最好是通过更简单的方式进行传递,比如方法参数等。

基于上面的考虑,在实际应用开发中,经常会简化中介者模式:

(1)通常会去掉同事对象的父类,这样可以让任意的交互的对象成为同事;

(2)通常不定义Mediator接口,把具体的中介者对象实现为单例;

(3)同事对象不再持有中介者对象,在需要的时候直接调用中介者对象;中介者对象也不再持有同事对象,在具体的处理方法里去创建,或者获取,或者从参数传入需要的同事对象;

(4)中介者对象根据实际的逻辑提供具体的业务通知方法,就像在上面“讲解”的最后一段讨论的那样。

把这样经过变形和简化的情况成为广义中介者模式。我觉得广义中介者模式更靠谱,更有实用价值。

管理系统中都有部分管理和人员管理,抽象出来部门类Dep和人员类User,经常需要处理的情况是人员离职,部门合并,人员转部门,撤销部门,不论是哪一种情况,都需要处理相应的人员和部门,人员对象和部门对象被紧密的耦合在一起。这时候就可以使用中介者模式了,可是部门类和人员类在逻辑上能有公共的父类吗?好像是不能有,所以这就需要使用广义中介者模式。中介者对象可以实现成单例,对于上面的每一种情况可以实现单独的业务方法,通过方法参数传入同事对象,不再持有同事对象。下面是示意代码:

/*部门类*/
public class Dep {
	//部门编号
	private String depId;
	//部门名称
	private String depName;
	//setter和getter
	//.......
	
	/*撤销部门,通知中介者模式*/
	public boolean deleteDep() {
		//获取中介者对象
		DepUserMediatorImp mediator = DepUserMediatorImp.getInstance();
		//通知中介者对象处理部门撤销事物
		mediator.deleteDep(depId);
		return true;
	}
}

/*人员类*/
class User {
	//人员编号
	private String userId;
	//人员名称
	private String userName;
	//setter和getter
	//.......
	
	/*撤销人员,通知中介者模式*/
	public boolean deleteUser() {
		//获取中介者对象
		DepUserMediatorImp mediator = DepUserMediatorImp.getInstance();
		//通知中介者对象处理人员撤销事物
		mediator.deleteUser(userId);
		return true;
	}
}
中介者对象
/*实现成单例类*/
public class DepUserMediatorImp {
	private static DepUserMediatorImp instance = new DepUserMediatorImp();
	
	/*部分和人员关系*/
	static class DepUserModel {
		private String depUserId;
		private String depId;
		private String userId;
		
		//setter,getter方法
	}
	//保存部门和人员关系
	private Collection<DepUserModel> model = ....;
	
	public static DepUserMediatorImp getInstance() {
		return instance;
	}
	
	/*部门撤销,通知中介者对象*/
	public boolean deleteDep(String depId) {
		//处理该部门人员的业务
		
		//删除当前model中与该部门有关系的对象
		Collection<DepUserModel> tmp = new ArrayList<DepUserModel>();
		for(DepUserModel m : model)
			if(m.getDepId().equals(depId))
				tmp.add(m);
		//删除
		model.removeAll(tmp);
	}
	
	/*人员离职,通知中介者对象*/
	public boolean deleteUser(String userId) {
		//删除当前model中与该部门有关系的对象
		Collection<DepUserModel> tmp = new ArrayList<DepUserModel>();
		for(DepUserModel m : model)
			if(m.getUserId().equals(userId))
				tmp.add(m);
		//删除
		model.removeAll(tmp);
	}
}

转载请注明出处:喻红叶《Java与模式-中介者模式》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值