目錄
本文的結構如下:
- 引言
- 什么是橋接模式
- 模式的結構
- 典型代碼
- 代碼示例
- 優點和缺點
- 適用環境
- 模式應用
一、引言
以往出門都要帶上錢包,準備一些現金,但自從有了支付寶,就再也沒怎么拿過錢包,口袋揣個手機就可以了,除了付款,手機游戲也很多,也可以看小說,可以說,已經離不開手機。
既然離不開手機,當然會給手機很多保護了,比如貼膜,戴上手機殼等等。廠家在生產手機時,都不是把手機和手機殼作為一個整體生產,然后出售,都是分開生產,分開銷售。
假如有三個品牌的手機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、缺點
橋接模式的主要缺點如下:
- 橋接模式的使用會增加系統的理解與設計難度,由于關聯關系建立在抽象層,要求開發者一開始就針對抽象層進行設計與編程。
- 橋接模式要求正確識別出系統中兩個獨立變化的維度,因此其使用范圍具有一定的局限性,如何正確識別兩個獨立維度也需要一定的經驗積累。
七、適用環境
在以下情況下可以考慮使用橋接模式:
- 如果一個系統需要在抽象化和具體化之間增加更多的靈活性,避免在兩個層次之間建立靜態的繼承關系,通過橋接模式可以使它們在抽象層建立一個關聯關系。
- “抽象部分”和“實現部分”可以以繼承的方式獨立擴展而互不影響,在程序運行時可以動態將一個抽象化子類的對象和一個實現化子類的對象進行組合,即系統需要對抽象化角色和實現化角色進行動態耦合。
- 一個類存在兩個(或多個)獨立變化的維度,且這兩個(或多個)維度都需要獨立進行擴展。
- 對于那些不希望使用繼承或因為多層繼承導致系統類的個數急劇增加的系統,橋接模式尤為適用。