目錄
本文的結(jié)構(gòu)如下:
- 引言
- 什么是橋接模式
- 模式的結(jié)構(gòu)
- 典型代碼
- 代碼示例
- 優(yōu)點(diǎn)和缺點(diǎn)
- 適用環(huán)境
- 模式應(yīng)用
一、引言
以往出門都要帶上錢包,準(zhǔn)備一些現(xiàn)金,但自從有了支付寶,就再也沒怎么拿過錢包,口袋揣個(gè)手機(jī)就可以了,除了付款,手機(jī)游戲也很多,也可以看小說,可以說,已經(jīng)離不開手機(jī)。
既然離不開手機(jī),當(dāng)然會(huì)給手機(jī)很多保護(hù)了,比如貼膜,戴上手機(jī)殼等等。廠家在生產(chǎn)手機(jī)時(shí),都不是把手機(jī)和手機(jī)殼作為一個(gè)整體生產(chǎn),然后出售,都是分開生產(chǎn),分開銷售。
假如有三個(gè)品牌的手機(jī)vivo,oppo和小米,如果手機(jī)手機(jī)殼一體生產(chǎn),會(huì)是這樣的:
對(duì)應(yīng)到相應(yīng)的類中,將是1+3+6=10個(gè)有繼承關(guān)系的類,如果這時(shí)再加一個(gè)華為手機(jī),無疑是要多增加3個(gè)類,會(huì)帶來類的急劇增長(zhǎng)。
如果手機(jī)手機(jī)殼分開生產(chǎn)搭配(現(xiàn)實(shí)也的確是這樣的),就是這樣的:
對(duì)應(yīng)到類設(shè)計(jì)中,只需要7個(gè)類,如果增加一類手機(jī),只需增加一個(gè)類,增加一款手機(jī)殼,也只需增加一個(gè)類。
這種處理多維變化(手機(jī)和手機(jī)殼)的方式運(yùn)用到軟件設(shè)計(jì)中就是橋接模式。
二、什么是橋接模式
橋接模式是一種很實(shí)用的結(jié)構(gòu)型設(shè)計(jì)模式,在軟件開發(fā)時(shí),如果某個(gè)類存在兩個(gè)獨(dú)立變化的維度,可以運(yùn)用橋接模式將這兩個(gè)維度分離出來,使兩者可以獨(dú)立擴(kuò)展,讓系統(tǒng)更加符合“單一職責(zé)原則”。
與多層繼承方案不同,它將兩個(gè)獨(dú)立變化的維度設(shè)計(jì)為兩個(gè)獨(dú)立的繼承等級(jí)結(jié)構(gòu),并且在抽象層建立一個(gè)抽象關(guān)聯(lián),該關(guān)聯(lián)關(guān)系就像一條橋一樣,將兩個(gè)獨(dú)立繼承結(jié)構(gòu)的類聯(lián)接起來,故名橋接模式。
可以明顯看出,橋接模式使用組合代替了繼承,將類之間的靜態(tài)繼承關(guān)系轉(zhuǎn)換為動(dòng)態(tài)的對(duì)象組合關(guān)系,使用組合而不用繼承,會(huì)使系統(tǒng)更加靈活,并易于擴(kuò)展,同時(shí)有效控制了系統(tǒng)中類的個(gè)數(shù)。
橋接定義如下:
橋接模式(Bridge Pattern):將抽象部分與它的實(shí)現(xiàn)部分分離,使它們都可以獨(dú)立地變化。它是一種對(duì)象結(jié)構(gòu)型模式,又稱為柄體(Handle and Body)模式或接口(Interface)模式。
上面的例子應(yīng)該清楚說明了什么是橋接模式,但回來看定義卻會(huì)發(fā)現(xiàn)有點(diǎn)模糊,大概是語義不是很清楚的緣故吧。
可以這樣理解:
抽象部分:面向?qū)ο笾校瑢?duì)象的共同性質(zhì)提取出來形成抽象類部分,比如上面的手機(jī)。
實(shí)現(xiàn)部分:是對(duì)抽象事物進(jìn)一步具體化的產(chǎn)物,比抽象部分更具體,比如給手機(jī)戴上手機(jī)殼成為有手機(jī)殼的手機(jī)就是一個(gè)實(shí)現(xiàn)化過程。
說白了就是在多維變化中,分離出其中的一個(gè)變化為單獨(dú)的繼承結(jié)構(gòu),在多重的繼承結(jié)構(gòu)中這層變化就是抽象的一種實(shí)現(xiàn),現(xiàn)在不用繼承來擴(kuò)展,而是改為組合來擴(kuò)展其實(shí)現(xiàn),也就是將抽象和實(shí)現(xiàn)分離。
三、模式的結(jié)構(gòu)
橋接模式UML類圖如下:
可以看到在橋接模式的結(jié)構(gòu)圖中,存在一條連接兩個(gè)繼承等級(jí)結(jié)構(gòu)的橋。
在橋接模式結(jié)構(gòu)圖中包含如下幾個(gè)角色:
- Abstraction(抽象類):用于定義抽象類的接口,它一般是抽象類而不是接口,其中定義了一個(gè)Implementor(實(shí)現(xiàn)類接口)類型的對(duì)象并可以維護(hù)該對(duì)象,它與Implementor之間具有關(guān)聯(lián)關(guān)系,它既可以包含抽象業(yè)務(wù)方法,也可以包含具體業(yè)務(wù)方法。
- RefinedAbstraction(擴(kuò)充抽象類):擴(kuò)充由Abstraction定義的接口,通常情況下它不再是抽象類而是具體類,它實(shí)現(xiàn)了在Abstraction中聲明的抽象業(yè)務(wù)方法,在RefinedAbstraction中可以調(diào)用在Implementor中定義的業(yè)務(wù)方法。
- Implementor(實(shí)現(xiàn)類接口):定義實(shí)現(xiàn)類的接口,這個(gè)接口不一定要與Abstraction的接口完全一致,事實(shí)上這兩個(gè)接口可以完全不同,一般而言,Implementor接口僅提供基本操作,而Abstraction定義的接口可能會(huì)做更多更復(fù)雜的操作。Implementor接口對(duì)這些基本操作進(jìn)行了聲明,而具體實(shí)現(xiàn)交給其子類。通過關(guān)聯(lián)關(guān)系,在Abstraction中不僅擁有自己的方法,還可以調(diào)用到Implementor中定義的方法,使用關(guān)聯(lián)關(guān)系來替代繼承關(guān)系。
- ConcreteImplementor(具體實(shí)現(xiàn)類):具體實(shí)現(xiàn)Implementor接口,在不同的ConcreteImplementor中提供基本操作的不同實(shí)現(xiàn),在程序運(yùn)行時(shí),ConcreteImplementor對(duì)象將替換其父類對(duì)象,提供給抽象類具體的業(yè)務(wù)操作方法。
橋接模式是一個(gè)非常有用的模式,在橋接模式中體現(xiàn)了很多面向?qū)ο笤O(shè)計(jì)原則的思想,包括“單一職責(zé)原則”、“開閉原則”、“合成復(fù)用原則”、“里氏代換原則”、“依賴倒轉(zhuǎn)原則”等。
在使用橋接模式時(shí),首先應(yīng)該識(shí)別出一個(gè)類所具有的兩個(gè)獨(dú)立變化的維度,將它們?cè)O(shè)計(jì)為兩個(gè)獨(dú)立的繼承等級(jí)結(jié)構(gòu),為兩個(gè)維度都提供抽象層,并建立抽象耦合。通常情況下,將具有兩個(gè)獨(dú)立變化維度的類的一些普通業(yè)務(wù)方法和與之關(guān)系最密切的維度設(shè)計(jì)為“抽象類”層次結(jié)構(gòu)(抽象部分),而將另一個(gè)維度設(shè)計(jì)為“實(shí)現(xiàn)類”層次結(jié)構(gòu)(實(shí)現(xiàn)部分)。
比如上面的例子,手機(jī)可以打電話,可以玩游戲,這些時(shí)每個(gè)手機(jī)型號(hào)都具備的業(yè)務(wù)方法,所以將手機(jī)型號(hào)這一維度定為手機(jī)的抽象部分,而手機(jī)殼則是另一個(gè)維度,與手機(jī)更多是一種“設(shè)置”關(guān)系,定為手機(jī)的實(shí)現(xiàn)部分。
四、典型代碼
一個(gè)類兩個(gè)獨(dú)立變化的維度,為了降低耦合度,對(duì)兩個(gè)不同的維度提取抽象類和實(shí)現(xiàn)類接口,并建立一個(gè)抽象關(guān)聯(lián)關(guān)系。對(duì)于“實(shí)現(xiàn)部分”維度,實(shí)現(xiàn)類接口典型代碼如下:
public interface Implementor {
void operationImpl();
}
具體實(shí)現(xiàn)類中實(shí)現(xiàn)了在實(shí)現(xiàn)類接中聲明的方法,典型代碼如下:
public class ConcreteImplementor implements Implementor {
public void operationImpl() {
//todo
}
}
另一維度,抽象類典型代碼如下:
public abstract class Abstraction {
protected Implementor impl; //實(shí)現(xiàn)類接口
public void setImpl(Implementor impl){
this.impl = impl;
}
public abstract void operation(); //聲明抽象業(yè)務(wù)方法
}
擴(kuò)充抽象類繼承抽象類,典型代碼如下:
public class RefinedAbstraction extends Abstraction {
public void operation() {
//todo
impl.operationImpl();
//todo
}
}
看上去有點(diǎn)像“多維度的裝飾者模式”。
五、代碼示例
還是以引言中的手機(jī)為例:
5.1、使用多重繼承
public abstract class Phone {
public abstract void playMusic();
}
public class VivoPhone extends Phone {
public void playMusic() {
System.out.println("音樂high起來");
}
}
public class OppoPhone extends Phone {
public void playMusic() {
System.out.println("音樂high起來");
}
}
public class XiaomiPhone extends Phone {
public void playMusic() {
System.out.println("音樂high起來");
}
}
public class SimpleVivoPhone extends VivoPhone {
public void playMusic() {
System.out.println("戴上簡(jiǎn)單手機(jī)殼");
System.out.println("音樂high起來");
}
}
public class CuteVivoPhone extends VivoPhone {
public void playMusic() {
System.out.println("戴上可愛手機(jī)殼");
System.out.println("音樂high起來");
}
}
public class SimpleOppoPhone extends OppoPhone {
public void playMusic() {
System.out.println("戴上簡(jiǎn)單手機(jī)殼");
System.out.println("音樂high起來");
}
}
public class CuteOppoPhone extends OppoPhone{
public void playMusic() {
System.out.println("戴上可愛手機(jī)殼");
System.out.println("音樂high起來");
}
}
public class SimpleXiaomiPhone extends XiaomiPhone {
public void playMusic() {
System.out.println("戴上簡(jiǎn)單手機(jī)殼");
System.out.println("音樂high起來");
}
}
public class CuteXiaomiPhone extends XiaomiPhone {
public void playMusic() {
System.out.println("戴上簡(jiǎn)單手機(jī)殼");
System.out.println("音樂high起來");
}
}
可以發(fā)現(xiàn):
- 由于采用了多層繼承結(jié)構(gòu),導(dǎo)致系統(tǒng)中類的個(gè)數(shù)急劇增加,系統(tǒng)擴(kuò)展麻煩。
- 從類的設(shè)計(jì)角度分析,具體類CuteXiaomiPhone、SimpleXiaomiPhone等違反了“單一職責(zé)原則”,因?yàn)椴恢挂粋€(gè)引起它們變化的原因,將播放音樂和戴手機(jī)殼完全不同的職責(zé)融合在一起,任意一個(gè)職責(zé)發(fā)生改變都需要修改它們。
5.2、使用橋接模式
先定出抽象部分:
public abstract class Phone {
protected ShellImplementor shellImplementor;
public void setShellImplementor(ShellImplementor shellImplementor){
this.shellImplementor = shellImplementor;
}
public void call(){
System.out.println("打電話");
}
public abstract void playMusic();
}
擴(kuò)展抽象部分:
public class VivoPhone extends Phone {
public void playMusic() {
shellImplementor.wearShell();
System.out.println("音樂High起來");
}
}
public class OppoPhone extends Phone {
public void playMusic() {
shellImplementor.wearShell();
System.out.println("音樂high起來");
}
}
public class XiaomiPhone extends Phone {
public void playMusic() {
shellImplementor.wearShell();
System.out.println("音樂high起來");
}
}
將手機(jī)殼部分定義為實(shí)現(xiàn)部分:
public interface ShellImplementor {
void wearShell();
}
具體實(shí)現(xiàn):
public class SimpleShell implements ShellImplementor {
public void wearShell() {
System.out.println("戴上簡(jiǎn)單手機(jī)殼");
}
}
public class CuteShell implements ShellImplementor {
public void wearShell() {
System.out.println("戴上可愛手機(jī)殼");
}
}
客戶端測(cè)試:
public class Client {
public static void main(String[] args) {
Phone phone = new VivoPhone();
ShellImplementor shell = new SimpleShell();
phone.setShellImplementor(shell);
phone.playMusic();
}
}
新增手機(jī)型號(hào)只需繼承自Phone就可以,新增手機(jī)殼也只需實(shí)現(xiàn)ShellImplementor,而且更換簡(jiǎn)單,可以用XML配置文件實(shí)現(xiàn),只需修改配置,不需修改源碼。
而且如果還要多一個(gè)實(shí)現(xiàn),即三重維度,比如手機(jī)膜,多重繼承會(huì)直接炸掉,而橋接模式則相對(duì)簡(jiǎn)潔很多。
六、優(yōu)點(diǎn)和缺點(diǎn)
6.1、優(yōu)點(diǎn)
橋接模式的主要優(yōu)點(diǎn)如下:
- 分離抽象接口及其實(shí)現(xiàn)部分。橋接模式使用“對(duì)象間的關(guān)聯(lián)關(guān)系”解耦了抽象和實(shí)現(xiàn)之間固有的綁定關(guān)系,使得抽象和實(shí)現(xiàn)可以沿著各自的維度來變化。所謂抽象和實(shí)現(xiàn)沿著各自維度的變化,也就是說抽象和實(shí)現(xiàn)不再在同一個(gè)繼承層次結(jié)構(gòu)中,而是“子類化”它們,使它們各自都具有自己的子類,以便任何組合子類,從而獲得多維度組合對(duì)象。
- 在很多情況下,橋接模式可以取代多層繼承方案,多層繼承方案違背了“單一職責(zé)原則”,復(fù)用性較差,且類的個(gè)數(shù)非常多,橋接模式是比多層繼承方案更好的解決方法,它極大減少了子類的個(gè)數(shù)。
- 橋接模式提高了系統(tǒng)的可擴(kuò)展性,在兩個(gè)變化維度中任意擴(kuò)展一個(gè)維度,都不需要修改原有系統(tǒng),符合“開閉原則”。
6.2、缺點(diǎn)
橋接模式的主要缺點(diǎn)如下:
- 橋接模式的使用會(huì)增加系統(tǒng)的理解與設(shè)計(jì)難度,由于關(guān)聯(lián)關(guān)系建立在抽象層,要求開發(fā)者一開始就針對(duì)抽象層進(jìn)行設(shè)計(jì)與編程。
- 橋接模式要求正確識(shí)別出系統(tǒng)中兩個(gè)獨(dú)立變化的維度,因此其使用范圍具有一定的局限性,如何正確識(shí)別兩個(gè)獨(dú)立維度也需要一定的經(jīng)驗(yàn)積累。
七、適用環(huán)境
在以下情況下可以考慮使用橋接模式:
- 如果一個(gè)系統(tǒng)需要在抽象化和具體化之間增加更多的靈活性,避免在兩個(gè)層次之間建立靜態(tài)的繼承關(guān)系,通過橋接模式可以使它們?cè)诔橄髮咏⒁粋€(gè)關(guān)聯(lián)關(guān)系。
- “抽象部分”和“實(shí)現(xiàn)部分”可以以繼承的方式獨(dú)立擴(kuò)展而互不影響,在程序運(yùn)行時(shí)可以動(dòng)態(tài)將一個(gè)抽象化子類的對(duì)象和一個(gè)實(shí)現(xiàn)化子類的對(duì)象進(jìn)行組合,即系統(tǒng)需要對(duì)抽象化角色和實(shí)現(xiàn)化角色進(jìn)行動(dòng)態(tài)耦合。
- 一個(gè)類存在兩個(gè)(或多個(gè))獨(dú)立變化的維度,且這兩個(gè)(或多個(gè))維度都需要獨(dú)立進(jìn)行擴(kuò)展。
- 對(duì)于那些不希望使用繼承或因?yàn)槎鄬永^承導(dǎo)致系統(tǒng)類的個(gè)數(shù)急劇增加的系統(tǒng),橋接模式尤為適用。