面向?qū)ο笤O(shè)計(jì)的設(shè)計(jì)模式:中介者模式
作者丨維他命君
來(lái)源丨程序員維他命(ID:J_Knight_)

定義
中介者模式(Mediator Pattern):用一個(gè)中介對(duì)象來(lái)封裝一系列的對(duì)象交互,中介者使各對(duì)象之間不需要顯式地相互引用,從而使其耦合松散,而且可以獨(dú)立地改變它們之間的交互。
適用場(chǎng)景
系統(tǒng)結(jié)構(gòu)可能會(huì)日益變得復(fù)雜,對(duì)象之間存在大量的相互關(guān)聯(lián)和調(diào)用,系統(tǒng)的整體結(jié)構(gòu)容易變?yōu)榫W(wǎng)狀結(jié)構(gòu)。在這種情況下,如果需要修改某一個(gè)對(duì)象,則可能會(huì)要跟蹤和該對(duì)象關(guān)聯(lián)的其他所有對(duì)象,并進(jìn)行處理。耦合越多,修改的地方就會(huì)越多。
如果我們使用中介者對(duì)象,則可以將系統(tǒng)的網(wǎng)狀結(jié)構(gòu)變成以中介者為中心的星型結(jié)構(gòu)。中介者承擔(dān)了中轉(zhuǎn)作用和協(xié)調(diào)作用,簡(jiǎn)化了對(duì)象之間的交互,而且還可以給對(duì)象間的交互進(jìn)行進(jìn)一步的控制。
現(xiàn)在我們清楚了中介者模式的適用場(chǎng)景,下面看一下中介者模式的成員和類圖。
成員與類圖
成員
中介者模式一共有四個(gè)成員:
抽象中介者(Mediator):抽象中介者定義具體中介者需要實(shí)現(xiàn)的接口。
具體中介者(Concrete Mediator):具體中介者實(shí)現(xiàn)抽象中介者定義的接口,承擔(dān)多個(gè)具體同事類之間的中介者的角色。
抽象同事類(Colleague):抽象同事類定義具體同事類需要實(shí)現(xiàn)的接口。
具體同事類(Concrete Colleague):具體同事類實(shí)現(xiàn)抽象同事類定義的接口。
模式類圖

代碼示例
場(chǎng)景概述
模擬一個(gè)多人對(duì)話的場(chǎng)景:當(dāng)一個(gè)人發(fā)出消息后,另外的那些人可以收到該消息。
場(chǎng)景分析
假設(shè)一共有A,B,C三個(gè)人,那么當(dāng)A發(fā)出消息后,需要分別傳遞給B,C二人。如果三個(gè)人直接相互通信,可能偽代碼會(huì)是這樣的:
A?sent?message?to?B
A?sent?message?to?C
而且隨著人數(shù)的增多,代碼行數(shù)也會(huì)變多,這顯然是不合理的。
因此在這種場(chǎng)景下,我們需要使用中介者模式,在所有人中間來(lái)做一個(gè)消息的多路轉(zhuǎn)發(fā):當(dāng)A發(fā)出消息后,由中介者來(lái)發(fā)送給B和C:
A?sent?message?to?Mediator?;
Mediator?sent?message?to?B?&?C
下面我們看一下如何用代碼來(lái)模擬該場(chǎng)景。
代碼實(shí)現(xiàn)
首先我們創(chuàng)建通話的用戶類User:
//==================?User.h?==================
@interface?User?:?NSObject
-?(instancetype)initWithName:(NSString?*)name?mediator:(ChatMediator?*)mediator;
-?(void)sendMessage:(NSString?*)message;
-?(void)receivedMessage:(NSString?*)message;
@end
//==================?User.m?==================
@implementation?User
{
????NSString?*_name;
????ChatMediator?*_chatMediator;
}
-?(instancetype)initWithName:(NSString?*)name?mediator:(ChatMediator?*)mediator{
????self?=?[super?init];
????if?(self)?{
????????_name?=?name;
????????_chatMediator?=?mediator;
????}
????return?self;
}
-?(void)sendMessage:(NSString?*)message{
????NSLog(@"================");
????NSLog(@"%@?sent?message:%@",_name,message);
????[_chatMediator?sendMessage:message?fromUser:self];
}
-?(void)receivedMessage:(NSString?*)message{
????NSLog(@"%@?has?received?message:%@",_name,message);
}
@end
用戶類在初始化的時(shí)候需要傳入中介者的實(shí)例,并持有。目的是為了在后面發(fā)送消息的時(shí)候把消息轉(zhuǎn)發(fā)給中介者。
另外,用戶類還對(duì)外提供了發(fā)送消息和接收消息的接口。而在發(fā)送消息的方法內(nèi)部其實(shí)調(diào)用的是中介者的發(fā)送消息的方法(因?yàn)橹薪檎叱钟辛怂杏脩舻膶?shí)例,因此可以做多路轉(zhuǎn)發(fā)),具體是如何做的我們可以看下中介者類ChatMediator的實(shí)現(xiàn):
//==================?ChatMediator.h?==================
@interface?ChatMediator?:?NSObject
-?(void)addUser:(User?*)user;
-?(void)sendMessage:(NSString?*)message?fromUser:(User?*)user;
@end
//==================?ChatMediator.m?==================
@implementation?ChatMediator
{
????NSMutableArray?*_userList;
}
-?(instancetype)init{
????self?=?[super?init];
????if?(self)?{
????????_userList?=?[NSMutableArray?array];
????}
????return?self;
}
-?(void)addUser:(User?*)user{
????[_userList?addObject:user];
}
-?(void)sendMessage:(NSString?*)message?fromUser:(User?*)user{
????[_userList?enumerateObjectsUsingBlock:^(User?*?_Nonnull?iterUser,?NSUInteger?idx,?BOOL?*?_Nonnull?stop)?{
????????if?(iterUser?!=?user)?{
????????????[iterUser?receivedMessage:message];
????????}
????}];
}
@end
中介者類提供了addUser:的方法,因此我們可以不斷將用戶添加到這個(gè)中介者里面(可以看做是注冊(cè)行為或是“加入群聊”)。在每次加入一個(gè)User實(shí)例后,都將這個(gè)實(shí)例添加到中介者持有的這個(gè)可變數(shù)組里。于是在將來(lái)中介者就可以通過(guò)遍歷數(shù)組的方式來(lái)做消息的多路轉(zhuǎn)發(fā),具體實(shí)現(xiàn)可以看sendMessage:fromUser:這個(gè)方法。
到現(xiàn)在為止,用戶類和中介者類都創(chuàng)建好了,我們看一下消息是如何轉(zhuǎn)發(fā)的:
ChatMediator?*cm?=?[[ChatMediator?alloc]?init];
User?*user1?=?[[User?alloc]?initWithName:@"Jack"?mediator:cm];
User?*user2?=?[[User?alloc]?initWithName:@"Bruce"?mediator:cm];
User?*user3?=?[[User?alloc]?initWithName:@"Lucy"?mediator:cm];
[cm?addUser:user1];
[cm?addUser:user2];
[cm?addUser:user3];
[user1?sendMessage:@"happy"];
[user2?sendMessage:@"new"];
[user3?sendMessage:@"year"];
從代碼中可以看到,我們這里創(chuàng)建了三個(gè)用戶,分別加入到了聊天中介者對(duì)象里。再后面我們分別讓每個(gè)用戶發(fā)送了一條消息。我們下面通過(guò)日至輸出來(lái)看一下每個(gè)用戶的消息接收情況:
[13806:1284059]?================
[13806:1284059]?Jack?sent?message:happy
[13806:1284059]?Bruce?has?received?message:happy
[13806:1284059]?Lucy?has?received?message:happy
[13806:1284059]?================
[13806:1284059]?Bruce?sent?message:new
[13806:1284059]?Jack?has?received?message:new
[13806:1284059]?Lucy?has?received?message:new
[13806:1284059]?================
[13806:1284059]?Lucy?sent?message:year
[13806:1284059]?Jack?has?received?message:year
[13806:1284059]?Bruce?has?received?message:year
下面看一下上面代碼對(duì)應(yīng)的類圖。
代碼對(duì)應(yīng)的類圖

優(yōu)點(diǎn)
中介者使各對(duì)象不需要顯式地相互引用,從而使其耦合松散。
缺點(diǎn)
在具體中介者類中包含了同事類之間的交互細(xì)節(jié),可能會(huì)導(dǎo)致具體中介者類非常復(fù)雜,使得其難以維護(hù)。
iOS SDK 和 JDK中的應(yīng)用
JDK中的
Timer就是中介者類的實(shí)現(xiàn),而配合使用的TimerTask則是同事類的實(shí)現(xiàn)。
-End-
最近有一些小伙伴,讓我?guī)兔φ乙恍?面試題?資料,于是我翻遍了收藏的 5T 資料后,匯總整理出來(lái),可以說(shuō)是程序員面試必備!所有資料都整理到網(wǎng)盤了,歡迎下載!

面試題】即可獲取