我们使用的电脑,你完成的任何一个功能都需要cpu、内存、显卡、键盘、显示器等这些零件相互调用才能完成功能,如果让这些零件之间直接互相调用,那么他们之间的关系可能如下图所示,非常凌乱复杂:
但是电脑开发商并没有让这些零件之间相互直接调用,而是通过主板来统一协调,这样每个零件只需要按照主板要求的接口去完成功能即可,然后把处理完成的数据传递给主板就可以了,不需要了解其他零件,此时结构如如下:
面向对象设计鼓励将行为分布到各个对象中。这种分布可能会导致对象间有许多连接。 在最坏的情况下 ,每一个对象都知道其他所有对象。
虽然将一个系统分割成许多对象通常可以增强可复用性 , 但是对象间相互连接的激增又会 降低其可复用性。大量的相互连接使得一个对象似乎不太可能在没有其他对象的支持下工作—--系统表现为一个不可分割的整体。而且对系统的行为进行任何较大的改动都十分困难, 因为行为被分布在许多对象中。结果是你可能不得不定义很多子类以定制系统的行为。今天讲解的中介者模式就是为了解决这个问题而诞生的。
定义
用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
中介者模式解决的困境就是多个对象之间的相互引用导致的紧耦合,通过引入一个中介者,原来互相引用的对象都变成同事类,他们之间现在没有任何关系,只和中介者交互,这样就实现了解耦。
这样以后如果增加或者修改任何同事类的功能,也只需要修改中介者,不需要修改其他同事类。
说白了中介者模式把对象之间的多对多转换成了一对多,从而降低耦合。
UML图及说明
上图是标准的中介者模式,其实在实际使用的时候会做一定程度的变形使用。下面加以说明:
1、是否需要mediator接口
接口就是用来实现面向接口编程,封装有多个中介者实现时的带来的变化。所以如果有多个中介者,那么就需要mediator接口,反之则不需要,实际场景中一般很少会有多个中介者,所以可以不需要接口
2、是否需要定义同事类的父类
其实在实际开发中,这些同事类不可能抽象为同一个父类,他们之间很少有共同性。所以没必要给他们定义一个共同父类
3、colleague和mediator是否需要相互持有
因为colleague和mediator需要相互通知自己的变化让对方做出反应,所以在标准实现中他们之间是相互持有的。其实可以把mediator做成单例,让同事类直接调用就好了。
同样的道理,mediator也没必要持有colleague,可以通过方法的参数吧colleague传递到mediator。
4、mediator只需要提供一个公共方法吗
在实际开发中,我们不仅要区分是哪个同事类传递过来的信息,还需要区分不同业务类型,所以需要根据实际需求定义多个公共方法。
5、mediator和colleague如何通信
标准模式里面是互相引用,然后告知对方,其实还可以使用观察者模式,让两者之间互相观察,有了变化就可以通知对方
实际场景运用
1、需求分析
假设我们使用电脑播放视频,把步骤分为如下几步
- 光驱读取光盘内容,把读取到的内容传递给主板
- 主板得到内容,交给cpu处理
- cpu处理完毕,把处理后的数据传递给主板
- 主板把数据传递给显卡,显卡显示视频(忽略了显示器显示这个步骤)
如果不适用中介者模式(加入主板),那么每个对象(零件)之间需要互相引用,耦合增加,导致复用修改困难。
3、代码实现
定义三个同事类:cpu、CDDriver、videoCard
#import <Foundation/Foundation.h>
@interface CPU : NSObject
-(void)executeData:(NSMutableString *)data;
@end
======
#import "CPU.h"
#import "mainBoard.h"
@implementation CPU
-(void )executeData:(NSMutableString *)data{
[data appendString:@"+经过cpu处理"];
[[mainBoard shareInstance] handleData:data dataSource:self];
}
@end
#import "CDDriver.h"
#import "mainBoard.h"
@implementation CDDriver
-(void)readCD{
NSString *data = @"BBC地球探索之旅";
NSMutableString *mStr = [[NSMutableString alloc]initWithString:data];
[[mainBoard shareInstance] handleData:mStr dataSource:self];
}
@end
#import "VideoCard.h"
@implementation VideoCard
-(void )executeData:(NSMutableString *)data{
[data appendString:@"+经过显卡处理"];
NSLog(@"开始播放视频:“%@",data);
}
@end
定义mediator类
#import <Foundation/Foundation.h>
@interface mainBoard : NSObject
+(instancetype)shareInstance;
-(void)handleData:(NSMutableString *)data dataSource:(id)source;
@end
=======================
#import "mainBoard.h"
#import "CPU.h"
#import "CDDriver.h"
#import "VideoCard.h"
static mainBoard *instance = nil;
@implementation mainBoard
+(instancetype)shareInstance{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if(instance == nil){
instance = [[self alloc]init];
}
});
return instance;
}
-(void)handleData:(NSMutableString *)data dataSource:(id)source{
if ([source isKindOfClass:[CDDriver class]]){
CPU *cpu = [CPU new];
[cpu executeData:data];
}else if ([source isKindOfClass:[CPU class]]){
VideoCard *video = [VideoCard new];
[video executeData:data];
}
}
@end
客户端调用:
#import <Foundation/Foundation.h>
#import "CDDriver.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
CDDriver *cd = [CDDriver new];
[cd readCD];
}
return 0;
}
通过上面的例子可以看到三个同事类的交互对象只有mainboard这个mediator类,它们之间互相不知道,减少了耦合。这里演示的只是这三个类实现的一个功能,实际情况是这三个类存在多种功能,比如播放音频,看文档。每个功能都需要这三个类之间的相互交互,那么在任何需要这些功能的地方,都需要引入这三个类,引入的地方越多,这三个类和其他类的耦合度就越高。
假设这三个类需要更改,那么牵一发动全身,其他所有引用这些类的地方都要改,但是如果引入中介者模式,则不会有这些问题。
优缺点
1、 减少了子类生成Mediator将原本分布于多个对象间的行为集中在一起 。 改 变 这 些 行 为,只需生成 Meditor的子类即可。这样各个 Colleage 类可被重用。
2、 它将各 Colleague 解耦 Mediator 有利于各 Coleague 间的松耦合 . 你 可 以 独 立 的 改 变 和 复
用各 Colleague 类和 Mediator 类。
3、它 简 化 了 对 象 协 议 用 Mediator 和各 Colleague 间 的 一 对 多 的 交 互 来 代 替 多 对 多 的 交 互 。一对多的关系更易于理解、维护和扩展。
4、它对对象如何协作进行了抽象 将中介作为一个独立的概念并将其封装在一个对象中,
使你将注意力从对象各自本身的行为转移到它们之间的交互上来。这有助于弄清楚一个系统 中的对象是如何交互的。
5、它使控制集中化。中介者模式将交互的复杂性变为中介者的复杂性。因为中介者封装了协议 , 它可能变得比任一个colleague都复杂。 这可能使得中介者自身成为一个难于维护的庞然大物。
何时使用
一组对象以定义良好但是复杂的方式进行通信。产生的相互依赖关系结构混乱且难以理解。
一个对象引用其他很多对象并且直接与这些对象通信,导致难以复用该对象。
想定制一个分布在多个类中的行为,而又不想生成太多的子类。