天天看點

設計模式--橋接模式目錄

目錄

本文的結構如下:

  • 引言
  • 什麼是橋接模式
  • 模式的結構
  • 典型代碼
  • 代碼示例
  • 優點和缺點
  • 适用環境
  • 模式應用

一、引言

以往出門都要帶上錢包,準備一些現金,但自從有了支付寶,就再也沒怎麼拿過錢包,口袋揣個手機就可以了,除了付款,手機遊戲也很多,也可以看小說,可以說,已經離不開手機。

既然離不開手機,當然會給手機很多保護了,比如貼膜,戴上手機殼等等。廠家在生産手機時,都不是把手機和手機殼作為一個整體生産,然後出售,都是分開生産,分開銷售。

假如有三個品牌的手機vivo,oppo和小米,如果手機手機殼一體生産,會是這樣的:

設計模式--橋接模式目錄

對應到相應的類中,将是1+3+6=10個有繼承關系的類,如果這時再加一個華為手機,無疑是要多增加3個類,會帶來類的急劇增長。

如果手機手機殼分開生産搭配(現實也的确是這樣的),就是這樣的:

設計模式--橋接模式目錄

對應到類設計中,隻需要7個類,如果增加一類手機,隻需增加一個類,增加一款手機殼,也隻需增加一個類。

這種處理多元變化(手機和手機殼)的方式運用到軟體設計中就是橋接模式。

二、什麼是橋接模式

橋接模式是一種很實用的結構型設計模式,在軟體開發時,如果某個類存在兩個獨立變化的次元,可以運用橋接模式将這兩個次元分離出來,使兩者可以獨立擴充,讓系統更加符合“單一職責原則”。

與多層繼承方案不同,它将兩個獨立變化的次元設計為兩個獨立的繼承等級結構,并且在抽象層建立一個抽象關聯,該關聯關系就像一條橋一樣,将兩個獨立繼承結構的類聯接起來,故名橋接模式。

可以明顯看出,橋接模式使用組合代替了繼承,将類之間的靜态繼承關系轉換為動态的對象組合關系,使用組合而不用繼承,會使系統更加靈活,并易于擴充,同時有效控制了系統中類的個數。

橋接定義如下:

橋接模式(Bridge Pattern):将抽象部分與它的實作部分分離,使它們都可以獨立地變化。它是一種對象結構型模式,又稱為柄體(Handle and Body)模式或接口(Interface)模式。

上面的例子應該清楚說明了什麼是橋接模式,但回來看定義卻會發現有點模糊,大概是語義不是很清楚的緣故吧。

可以這樣了解:

抽象部分:面向對象中,将對象的共同性質提取出來形成抽象類部分,比如上面的手機。

實作部分:是對抽象事物進一步具體化的産物,比抽象部分更具體,比如給手機戴上手機殼成為有手機殼的手機就是一個實作化過程。

說白了就是在多元變化中,分離出其中的一個變化為單獨的繼承結構,在多重的繼承結構中這層變化就是抽象的一種實作,現在不用繼承來擴充,而是改為組合來擴充其實作,也就是将抽象和實作分離。

三、模式的結構

橋接模式UML類圖如下:

設計模式--橋接模式目錄

可以看到在橋接模式的結構圖中,存在一條連接配接兩個繼承等級結構的橋。

在橋接模式結構圖中包含如下幾個角色:

  • Abstraction(抽象類):用于定義抽象類的接口,它一般是抽象類而不是接口,其中定義了一個Implementor(實作類接口)類型的對象并可以維護該對象,它與Implementor之間具有關聯關系,它既可以包含抽象業務方法,也可以包含具體業務方法。
  • RefinedAbstraction(擴充抽象類):擴充由Abstraction定義的接口,通常情況下它不再是抽象類而是具體類,它實作了在Abstraction中聲明的抽象業務方法,在RefinedAbstraction中可以調用在Implementor中定義的業務方法。
  • Implementor(實作類接口):定義實作類的接口,這個接口不一定要與Abstraction的接口完全一緻,事實上這兩個接口可以完全不同,一般而言,Implementor接口僅提供基本操作,而Abstraction定義的接口可能會做更多更複雜的操作。Implementor接口對這些基本操作進行了聲明,而具體實作交給其子類。通過關聯關系,在Abstraction中不僅擁有自己的方法,還可以調用到Implementor中定義的方法,使用關聯關系來替代繼承關系。
  • ConcreteImplementor(具體實作類):具體實作Implementor接口,在不同的ConcreteImplementor中提供基本操作的不同實作,在程式運作時,ConcreteImplementor對象将替換其父類對象,提供給抽象類具體的業務操作方法。

橋接模式是一個非常有用的模式,在橋接模式中展現了很多面向對象設計原則的思想,包括“單一職責原則”、“開閉原則”、“合成複用原則”、“裡氏代換原則”、“依賴倒轉原則”等。

在使用橋接模式時,首先應該識别出一個類所具有的兩個獨立變化的次元,将它們設計為兩個獨立的繼承等級結構,為兩個次元都提供抽象層,并建立抽象耦合。通常情況下,将具有兩個獨立變化次元的類的一些普通業務方法和與之關系最密切的次元設計為“抽象類”層次結構(抽象部分),而将另一個次元設計為“實作類”層次結構(實作部分)。

比如上面的例子,手機可以打電話,可以玩遊戲,這些時每個手機型号都具備的業務方法,是以将手機型号這一次元定為手機的抽象部分,而手機殼則是另一個次元,與手機更多是一種“設定”關系,定為手機的實作部分。

四、典型代碼

一個類兩個獨立變化的次元,為了降低耦合度,對兩個不同的次元提取抽象類和實作類接口,并建立一個抽象關聯關系。對于“實作部分”次元,實作類接口典型代碼如下:

public interface Implementor {
    void operationImpl();
}
           

具體實作類中實作了在實作類接中聲明的方法,典型代碼如下:

public class ConcreteImplementor implements Implementor {
    public void operationImpl() {
        //todo
    }
}
           

另一次元,抽象類典型代碼如下:

public abstract class Abstraction {
    protected Implementor impl; //實作類接口

    public void setImpl(Implementor impl){
        this.impl = impl;
    }

    public abstract void operation();  //聲明抽象業務方法
}
           

擴充抽象類繼承抽象類,典型代碼如下:

public class RefinedAbstraction extends Abstraction {
    public void operation() {
        //todo
        impl.operationImpl();
        //todo
    }
}
           

看上去有點像“多元度的裝飾者模式”。

五、代碼示例

還是以引言中的手機為例:

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("戴上簡單手機殼");
        System.out.println("音樂high起來");
    }
}

public class CuteVivoPhone extends VivoPhone {
    public void playMusic() {
        System.out.println("戴上可愛手機殼");
        System.out.println("音樂high起來");
    }
}

public class SimpleOppoPhone extends OppoPhone {
    public void playMusic() {
        System.out.println("戴上簡單手機殼");
        System.out.println("音樂high起來");
    }
}

public class CuteOppoPhone extends OppoPhone{
    public void playMusic() {
        System.out.println("戴上可愛手機殼");
        System.out.println("音樂high起來");
    }
}

public class SimpleXiaomiPhone extends XiaomiPhone {
    public void playMusic() {
        System.out.println("戴上簡單手機殼");
        System.out.println("音樂high起來");
    }
}

public class CuteXiaomiPhone extends XiaomiPhone {
    public void playMusic() {
        System.out.println("戴上簡單手機殼");
        System.out.println("音樂high起來");
    }
}
           

可以發現:

  • 由于采用了多層繼承結構,導緻系統中類的個數急劇增加,系統擴充麻煩。
  • 從類的設計角度分析,具體類CuteXiaomiPhone、SimpleXiaomiPhone等違反了“單一職責原則”,因為不止一個引起它們變化的原因,将播放音樂和戴手機殼完全不同的職責融合在一起,任意一個職責發生改變都需要修改它們。

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();
}
           

擴充抽象部分:

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起來");
    }
}
           

将手機殼部分定義為實作部分:

public interface ShellImplementor {
    void wearShell();
}
           

具體實作:

public class SimpleShell implements ShellImplementor {
    public void wearShell() {
        System.out.println("戴上簡單手機殼");
    }
}

public class CuteShell implements ShellImplementor {
    public void wearShell() {
        System.out.println("戴上可愛手機殼");
    }
}
           

用戶端測試:

public class Client {
    public static void main(String[] args) {
        Phone phone = new VivoPhone();
        ShellImplementor shell = new SimpleShell();
        phone.setShellImplementor(shell);
        phone.playMusic();
    }
}
           

新增手機型号隻需繼承自Phone就可以,新增手機殼也隻需實作ShellImplementor,而且更換簡單,可以用XML配置檔案實作,隻需修改配置,不需修改源碼。

而且如果還要多一個實作,即三重次元,比如手機膜,多重繼承會直接炸掉,而橋接模式則相對簡潔很多。

六、優點和缺點

6.1、優點

橋接模式的主要優點如下:

  • 分離抽象接口及其實作部分。橋接模式使用“對象間的關聯關系”解耦了抽象和實作之間固有的綁定關系,使得抽象和實作可以沿着各自的次元來變化。所謂抽象和實作沿着各自次元的變化,也就是說抽象和實作不再在同一個繼承層次結構中,而是“子類化”它們,使它們各自都具有自己的子類,以便任何組合子類,進而獲得多元度組合對象。
  • 在很多情況下,橋接模式可以取代多層繼承方案,多層繼承方案違背了“單一職責原則”,複用性較差,且類的個數非常多,橋接模式是比多層繼承方案更好的解決方法,它極大減少了子類的個數。
  • 橋接模式提高了系統的可擴充性,在兩個變化次元中任意擴充一個次元,都不需要修改原有系統,符合“開閉原則”。

6.2、缺點

橋接模式的主要缺點如下:

  • 橋接模式的使用會增加系統的了解與設計難度,由于關聯關系建立在抽象層,要求開發者一開始就針對抽象層進行設計與程式設計。
  • 橋接模式要求正确識别出系統中兩個獨立變化的次元,是以其使用範圍具有一定的局限性,如何正确識别兩個獨立次元也需要一定的經驗積累。

七、适用環境

在以下情況下可以考慮使用橋接模式:

  • 如果一個系統需要在抽象化和具體化之間增加更多的靈活性,避免在兩個層次之間建立靜态的繼承關系,通過橋接模式可以使它們在抽象層建立一個關聯關系。
  • “抽象部分”和“實作部分”可以以繼承的方式獨立擴充而互不影響,在程式運作時可以動态将一個抽象化子類的對象和一個實作化子類的對象進行組合,即系統需要對抽象化角色和實作化角色進行動态耦合。
  • 一個類存在兩個(或多個)獨立變化的次元,且這兩個(或多個)次元都需要獨立進行擴充。
  • 對于那些不希望使用繼承或因為多層繼承導緻系統類的個數急劇增加的系統,橋接模式尤為适用。

繼續閱讀