設(shè)計(jì)模式--橋接模式

目錄

本文的結(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ì)是這樣的:

image

對(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í)也的確是這樣的),就是這樣的:

image

對(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類圖如下:

image

可以看到在橋接模式的結(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、使用多重繼承

image
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、使用橋接模式

image

先定出抽象部分:

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),橋接模式尤為適用。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,460評(píng)論 6 538
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,067評(píng)論 3 423
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,467評(píng)論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,468評(píng)論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,184評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,582評(píng)論 1 325
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,616評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,794評(píng)論 0 289
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,343評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,096評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,291評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,863評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,513評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,941評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,190評(píng)論 1 291
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,026評(píng)論 3 396
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,253評(píng)論 2 375

推薦閱讀更多精彩內(nèi)容