假設要實現一個給客戶傳送提示訊息的功能,傳送的訊息型別可分為:普通訊息、加急訊息、特加急訊息等等,而每種訊息的傳送的方式一般有:系統內推送、手機簡訊、電子郵件等等。如果讓我們來實現,會怎麼做呢?
我們先來實現一個簡單的版本,使用系統推送和電子郵件傳送普通訊息,實現起來不叫簡單,就不展示程式碼了,直接看UML結構圖
很簡單的實現對吧,現在再增加一個加急訊息的傳送,也是通過系統推送和Email兩種方式傳送,而且加急訊息還額外多了一個方法,想了想簡單嘛,直接擴充套件現有的介面不就可以了嗎,此時UML結構如如下:
如果在增加一個特加急訊息的傳送呢,也是兩種方式,也有一個額外的方法,繼續擴充套件嘛,此時UML機構如如下:
是不是感覺類的數目劇增了,還沒完呢。現在需要給每種訊息型別增加一種傳送方式:手機傳送。繼續改,UML結構圖如下:
此時類的數目已經非常多了,如果繼續增加訊息型別或者傳送方式,那麼又要重複擴充套件,類的數目會急劇增加。
我們來仔細分析下上面的實現,其實這裡面有兩個變化的維度:訊息型別和傳送方式。他們之間是交織在一起的,如下圖所示:
由於這兩個維度是交織在一起,那麼他們之間的組合方式就有9種,也就是我們上面看到的有9個類,如果此時任何一個維度發生變化,都會導致與之關聯的另一個維度也需要修改。而且一個維度的上的數目的增加,都會導致整體類的數目成倍數的增加。如果訊息的傳送型別和傳送方式都有10種的話,那麼就需要實現100個類,想想就恐怖。
那麼自然而然的我們就想到把這兩個維度分開,讓他們獨自變化,互不影響,需要用的是會把他們組合在一起就行了。這樣一個維度的類增加,不會導致另一個維度類也需要跟著一起增加。
再次證明:多用組合少用繼承
那麼如何實現呢?這就要請出我們今天的主角:橋接模式。
定義
將抽象部分與它的實現部分分離,使它們都可以獨立地變化。
抽象部分和實現部分就是兩個不同的維度,抽象部分對應上面的訊息型別,實現部分對應上面的訊息傳送方式。現在我們獨立實現兩個部分,然後訊息型別部分想呼叫訊息實現部分去傳送訊息該怎麼辦呢?很簡單嘛,讓抽象部分持有實現部分的介面,面向介面程式設計就可以了,這就是橋接模式名字的由來。橋接抽象部分和實現部分,下面看UML結構圖會更加的清晰。
UML結構如及說明
可以看到抽象部分的抽象類和實現部分的介面是聚合關係,表示抽象部分持有實現部門的介面,這樣抽象部分就可以呼叫實現部分完成功能了。
分析到這裡,大家應該對橋接模式有一個大致的瞭解了吧,下面就來看看如何使用橋接模式來實現上面的訊息傳送功能。
程式碼實現
1、訊息型別抽象類
#import <Foundation/Foundation.h>
#import "messageImplement.h"
@interface abstractMessage : NSObject
@property(strong,nonatomic)id<messageImplement> messageIm;
-(void)send:(NSMutableString*)message;
- (instancetype)initWithImplement:(id<messageImplement>)implement;
@end
====================
#import "abstractMessage.h"
@implementation abstractMessage
- (instancetype)initWithImplement:(id<messageImplement>)implement
{
self = [super init];
if (self) {
self.messageIm = implement;
}
return self;
}
-(void)send:(NSMutableString*)message{
}
@end複製程式碼
2、具體訊息型別
下面只展示了普通訊息的具體類實現,其他兩種方式類似,詳細見demo
#import "abstractMessage.h"
@interface commonMessage : abstractMessage
@end
====================
#import "commonMessage.h"
@implementation commonMessage
-(void)send:(NSMutableString *)message{
[message insertString:@"【普通訊息:" atIndex:0];
[message appendString:@"】"];
[self.messageIm sendMessage:message];
}
@end複製程式碼
3、訊息傳送介面
#import <Foundation/Foundation.h>
@protocol messageImplement <NSObject>
-(void)sendMessage:(NSString *)message;
@end複製程式碼
4、具體的訊息傳送方式
下面只展示使用系統內推送方式傳送訊息的方式,其他兩種訊息傳送方式類似,不在展示,具體見demo
#import <Foundation/Foundation.h>
#import "messageImplement.h"
@interface messageSMS : NSObject<messageImplement>
@end
=================
#import "messageSMS.h"
@implementation messageSMS
-(void)sendMessage:(NSString *)message{
NSLog(@"使用系統內訊息方式傳送訊息,訊息內容:%@", message);
}
@end複製程式碼
5、測試
id<messageImplement> messageIMP = [messageMobile new];
abstractMessage *message = [[specialUrgencyMessage alloc]initWithImplement:messageIMP];
NSMutableString *mStr = [[NSMutableString alloc]initWithString:@"大海啊,全是水,駿馬啊,四條腿"];
[message send:mStr];複製程式碼
6、輸出
2016-12-15 16:56:43.356 橋接模式[66573:2541266] 使用手機方式傳送訊息,訊息內容:【特別加急訊息:大海啊,全是水,駿馬啊,四條腿】複製程式碼
你可以任意組合訊息型別和訊息傳送方式,此時類的數目只有6個,比之前的繼承實現方式9個少了。如果兩者都有10種實現方式,那麼使用繼承方式就需要100個類,而使用橋接模式只要20個類,看到了橋接模式的巨大優點吧。
優缺點
思考
橋接模式,需要理解橋接二字的由來,看上面的UML圖就明白了,是在抽象和實現之間橋接。因為他們現在分開了,但是抽象部分必須使用實現部分去實現功能,所以抽象部分必須引用實現部分,這就是橋接。
橋接模式對比繼承的有點是把本來混在一起的兩個變化未讀分開,讓他們獨立變化,這樣互相不影響,減少了類的數目,也方便擴充套件,而且可以動態替換功能。比如上面的訊息傳送功能,同樣是傳送普通訊息,我可以選擇手機、email其中的任何一種方式,只需要組合起來就行了,比繼承更加靈活。
擴充套件下去,我們上面只是兩個維度在變化,那麼如果是三個、四個維度在變化呢?如果你使用繼承去實現,那就完了,不知道要重複寫多少程式碼,而使用橋接模式,就可以把這些變化維度全部獨立分開實現,然後客戶端想怎麼組合就怎麼組合。