引言
在項(xiàng)目開發(fā)中,我們會(huì)遇到這樣的一種場景:某些類型由于自身的邏輯,往往具有兩個(gè)或多個(gè)維度的變化,比如說大話設(shè)計(jì)模式書中所說的手機(jī),它有兩個(gè)變化的維度:一是手機(jī)的品牌,可能有三星、蘋果等;二是手機(jī)上的軟件,可能有QQ、微信等。如何應(yīng)對(duì)這種“多維度的變化”?怎樣利用面向?qū)ο蟮募夹g(shù)來使得該類型能夠輕松的沿著多個(gè)方向進(jìn)行變化,而又不引入額外的復(fù)雜度?這就是本章橋接模式所要解決的問題。
何為橋接模式?
橋接模式的目的是把抽象層次結(jié)構(gòu)從其實(shí)現(xiàn)中分離出來,使其能夠獨(dú)立變更。抽象層定義了供客戶端使用的上層的抽象接口。實(shí)現(xiàn)層定義了供抽象層使用的底層接口。實(shí)現(xiàn)類的引用被封裝于抽象層的實(shí)例中,橋接就形成。(與外觀模式有一定的相似之處)。
橋接模式:將抽象部分與它的實(shí)現(xiàn)部分分離,使它們都可以獨(dú)立地變化。
橋接模式的實(shí)例應(yīng)用
比如有一家電視機(jī)制造商,他們生產(chǎn)的每臺(tái)電視都帶一個(gè)遙控器,用戶可以用遙控器進(jìn)行頻道切換之類的操作。在這里遙控器是控制電視機(jī)的接口,如果每個(gè)電視機(jī)型號(hào)需要一個(gè)專用的遙控器,那么單是遙控器就會(huì)導(dǎo)致設(shè)計(jì)激增。不過,每個(gè)遙控器都有些功能是各種型號(hào)電視機(jī)共有的,比如切換頻道、調(diào)節(jié)音量和電源開關(guān)。而且每臺(tái)電視機(jī)都應(yīng)該能夠通過基本命令接口,響應(yīng)遙控器發(fā)來的這些命令。我們可以把遙控器邏輯同實(shí)際的電視機(jī)型號(hào)分離開來。這樣電視機(jī)型號(hào)的改變就不會(huì)對(duì)遙控器的設(shè)計(jì)有任何的影響。遙控器的同一個(gè)設(shè)計(jì)可以被復(fù)用和擴(kuò)展,而不會(huì)影響其他電視機(jī)型號(hào)。如下圖所示:
AbstractRemoteControl是定義了供客戶端使用的上層接口的父接口。它有一個(gè)對(duì)TVProtocol視力的引用,TVProtocol定義了實(shí)現(xiàn)類的接口。這個(gè)接口不必跟AbstractRemoteControl的接口一致,其實(shí)兩個(gè)接口可以完全不同。TVProtocol的接口提供基本的操作,而AbstractRemoteControl的上層操作基于這些基本操作。當(dāng)客戶端向AbstractRemoteControl的實(shí)例發(fā)送operation消息時(shí),這個(gè)方法向imp發(fā)送operationImp消息,底下的實(shí)際由TVA或TVB將作出響應(yīng)并接受任務(wù)。
因此想要往系統(tǒng)中添加新的TVProtocol時(shí),所要做的只是為TVProtocol創(chuàng)建一個(gè)新的實(shí)現(xiàn)類,響應(yīng)operationImp消息并在其中執(zhí)行任何具體的操作。不過,這對(duì)AbstractRemoteControl方面不會(huì)有任何影響。同樣,如果想修改AbstractRemoteControl的接口或者創(chuàng)建更細(xì)化的AbstractRemoteControl類,也不會(huì)影響橋接的另一頭。
來看下具體的代碼實(shí)現(xiàn),先看下抽象部分的代碼實(shí)現(xiàn),AbstractRemoteControl代碼如下:
#import <Foundation/Foundation.h>
#import "TVProtocol.h"
@interface AbstractRemoteControl : NSObject
@property (nonatomic, weak) id<TVProtocol> tvProtocol;
- (void)detectTVFunction;
@end
#import "AbstractRemoteControl.h"
@implementation AbstractRemoteControl
- (void)detectTVFunction {
NSLog(@"檢測電視機(jī)具備的功能,由子類來進(jìn)行實(shí)現(xiàn)");
}
@end
在AbstractRemoteControl類中保持了對(duì)TVProtocol實(shí)例對(duì)象的引用,定義了供客戶端使用的上層抽象接口detectTVFunction,而這個(gè)方法的具體實(shí)現(xiàn)則由其子類去實(shí)現(xiàn),ConcreteRemoteControl代碼如下:
#import "AbstractRemoteControl.h"
@interface ConcreteRemoteControl : AbstractRemoteControl
// 重寫該方法
- (void)detectTVFunction;
@end
#import "ConcreteRemoteControl.h"
@implementation ConcreteRemoteControl
- (void)detectTVFunction {
[self.tvProtocol switchChannel];
[self.tvProtocol adjustVolume];
[self.tvProtocol powerSwitch];
}
@end
從這里我們可以看出,當(dāng)客戶端向ConcreteRemoteControl的實(shí)例發(fā)送detectTVFunction消息時(shí),這個(gè)方法向TVProtocol發(fā)送switchChannel、adjustVolume、powerSwitch三個(gè)消息,TVA或TVB將作出響應(yīng)并接受任務(wù)。至此,抽象部分代碼已經(jīng)完成了,接著看下實(shí)現(xiàn)部分的代碼,TVProtocol代碼如下:
#import <Foundation/Foundation.h>
@protocol TVProtocol <NSObject>
@required
- (void)switchChannel; // 切換頻道
- (void)adjustVolume; // 調(diào)節(jié)音量
- (void)powerSwitch; // 電源開關(guān)
@end
這就是一個(gè)協(xié)議,協(xié)議里面定義了三個(gè)方法,以后在創(chuàng)建電視機(jī)實(shí)例的時(shí)候,就必須遵守該協(xié)議,從而保證了電視機(jī)具有相同的功能。AbstractTV的代碼如下:
#import <Foundation/Foundation.h>
#import "TVProtocol.h"
@interface AbstractTV : NSObject <TVProtocol>
@end
#import "AbstractTV.h"
@implementation AbstractTV
- (void)switchChannel {
NSLog(@"切換頻道,由具體的子類來實(shí)現(xiàn)");
}
- (void)adjustVolume {
NSLog(@"調(diào)節(jié)音量,由具體的子類來實(shí)現(xiàn)");
}
- (void)powerSwitch {
NSLog(@"電源開關(guān),由具體的子類來實(shí)現(xiàn)");
}
@end
TVA的代碼如下:
#import "AbstractTV.h"
@interface TVA : AbstractTV
// 重寫這三個(gè)方法
- (void)switchChannel;
- (void)adjustVolume;
- (void)powerSwitch;
@end
#import "TVA.h"
@implementation TVA
- (void)switchChannel {
NSLog(@"電視機(jī)A 具備了切換頻道的功能");
}
- (void)adjustVolume {
NSLog(@"電視機(jī)A 具備了調(diào)節(jié)音量的功能");
}
- (void)powerSwitch {
NSLog(@"電視機(jī)A 具備了電源開關(guān)的功能");
}
@end
TVB的代碼如下:
#import "AbstractTV.h"
@interface TVB : AbstractTV
// 重寫這三個(gè)方法
- (void)switchChannel;
- (void)adjustVolume;
- (void)powerSwitch;
@end
#import "TVB.h"
@implementation TVB
- (void)switchChannel {
NSLog(@"電視機(jī)B 具備了切換頻道的功能");
}
- (void)adjustVolume {
NSLog(@"電視機(jī)B 具備了調(diào)節(jié)音量的功能");
}
- (void)powerSwitch {
NSLog(@"電視機(jī)B 具備了電源開關(guān)的功能");
}
@end
到這里,橋接模式代碼已經(jīng)完成了,在客戶端該怎么去應(yīng)用呢?我們通過下面的客戶端代碼來說明,如下:
#import "ViewController.h"
#import "AbstractRemoteControl.h"
#import "ConcreteRemoteControl.h"
#import "TVProtocol.h"
#import "AbstractTV.h"
#import "TVA.h"
#import "TVB.h"
typedef id<TVProtocol> TVProtocol; //在這里要進(jìn)行一下轉(zhuǎn)換聲明,否則類中不能識(shí)別TVProtocol.
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
AbstractRemoteControl *remoteControl = [[ConcreteRemoteControl alloc] init];
TVProtocol tvProtocol = [[TVA alloc] init];
remoteControl.tvProtocol = tvProtocol;
[remoteControl detectTVFunction];
NSLog(@"///////////////////////////////");
tvProtocol = [[TVB alloc] init];
remoteControl.tvProtocol = tvProtocol;
[remoteControl detectTVFunction];
/**
* 橋接模式:將抽象部分與它的實(shí)現(xiàn)部分分離,使它們都可以獨(dú)立地變化。
* 在本例中,AbstractRemoteControl是抽象部分,TVProtocol是其實(shí)現(xiàn)部分。
*/
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
日志輸出如下:
2015-09-01 22:59:06.295 Bridge[16464:703747] 電視機(jī)A 具備了切換頻道的功能2015-09-01 22:59:06.295 Bridge[16464:703747] 電視機(jī)A 具備了調(diào)節(jié)音量的功能2015-09-01 22:59:06.296 Bridge[16464:703747] 電視機(jī)A 具備了電源開關(guān)的功能2015-09-01 22:59:06.296 Bridge[16464:703747] ///////////////////////////////2015-09-01 22:59:06.296 Bridge[16464:703747] 電視機(jī)B 具備了切換頻道的功能2015-09-01 22:59:06.296 Bridge[16464:703747] 電視機(jī)B 具備了調(diào)節(jié)音量的功能2015-09-01 22:59:06.296 Bridge[16464:703747] 電視機(jī)B 具備了電源開關(guān)的功能
通過橋接模式的應(yīng)用,我們可以把抽象部分與實(shí)現(xiàn)部分分離,使它們都可以獨(dú)立的變化。比如在本例中,對(duì)AbstractRemoteControl的修改,不會(huì)影響到TVProtocol。同樣對(duì)TVProtocol的修改,也不會(huì)影響AbstractRemoteControl。這正是橋接模式帶給我們的便利性。
小結(jié)
總的來說,橋接模式的本質(zhì)在于“分離抽象和實(shí)現(xiàn)”。
橋接模式的優(yōu)點(diǎn):
橋接模式使用聚合關(guān)系,解耦了抽象和實(shí)現(xiàn)之間固有的綁定關(guān)系,使得抽象和實(shí)現(xiàn)可以沿著各自的維度來變化。
提高了系統(tǒng)的可擴(kuò)展性,可以獨(dú)立地對(duì)抽象部分和實(shí)現(xiàn)部分進(jìn)行擴(kuò)展。
可減少子類的個(gè)數(shù),這個(gè)在前面講手機(jī)示例的時(shí)候進(jìn)行分析了。
橋接模式的缺點(diǎn):
橋接模式的引入會(huì)增加系統(tǒng)的理解與設(shè)計(jì)難度,由于聚合關(guān)系建立在抽象層,要求開發(fā)者針對(duì)抽象進(jìn)行設(shè)計(jì)與編程。
橋接模式要求正確識(shí)別出系統(tǒng)中兩個(gè)獨(dú)立變化的維度,因此其使用范圍具有一定的局限性。
適用場景
通過優(yōu)缺點(diǎn)的分析,我們可以在如下的情形下使用橋接模式:
不想在抽象與其實(shí)現(xiàn)之間形成固定的綁定關(guān)系;
抽象及其實(shí)現(xiàn)都應(yīng)可以通過子類化獨(dú)立進(jìn)行擴(kuò)展;
對(duì)抽象的實(shí)現(xiàn)進(jìn)行修改不應(yīng)影響客戶端代碼;
如果每個(gè)實(shí)現(xiàn)需要額外的子類以細(xì)化抽象,則說明有必要把它們分成兩個(gè)部分;
想在帶有不同抽象接口的多個(gè)對(duì)象之間共享一個(gè)實(shí)現(xiàn)。




















