一、什么是橋接模式
橋接模式,又叫橋梁模式,顧名思義,就是有座“橋”,那這座橋是什么呢?就是一條聚合線(下方UML圖),比如我們下面會舉的例子,手機有手機品牌和手機游戲等等,每個手機品牌都有多款游戲,那是不是二者之間就是聚合關系了,這是合成/聚合復用原則的體現,當我們發現類有多層繼承時就可以考慮使用橋接模式,用聚合代替繼承。
橋接模式(Bridge),將抽象部分與它的實現部分分離,使它們都可以獨立地變化。UML結構圖如下:
其中,Abstraction為抽象化角色,定義出該角色的行為,同時保存一個對實現化角色的引用;Implementor是實現化角色,它是接口或者抽象類,定義角色必需的行為和屬性;RefinedAbstraction為修正抽象化角色,引用實現化角色對抽象化角色進行修正;ConcreteImplementor為具體實現化角色,實現接口或抽象類定義的方法或屬性。
是不是感覺上面這段話很難懂,其實說簡單點就是在Abstraction和Implementor之間架了一座橋(聚合線),這里體現了一個原則就是合成/聚合復用原則,具體看目錄篇對基本原則的講解及舉例。下面放上模板代碼。
1. Abstraction抽象類
1 public abstract class Abstraction { 2 3 private Implementor imp; 4 5 //約束子類必須實現該構造函數 6 public Abstraction(Implementor imp) { 7 this.imp = imp; 8 } 9 10 public Implementor getImp() { 11 return imp; 12 } 13 14 //自身的行為和屬性 15 public void request() { 16 this.imp.doSomething(); 17 } 18 19 }
2. Implementor抽象類
1 public abstract class Implementor { 2 3 public abstract void doSomething(); 4 public abstract void doAnything(); 5 6 }
3. ConcreteImplementor
這里可以編寫多個具體實現類。
1 public class ConcreteImplementorA extends Implementor { 2 3 @Override 4 public void doSomething() { 5 System.out.println("具體實現A的doSomething執行"); 6 } 7 8 @Override 9 public void doAnything() { 10 System.out.println("具體實現A的doAnything執行"); 11 } 12 }
4. RefinedAbstraction
1 public class RefinedAbstraction extends Abstraction { 2 3 //覆寫構造函數 4 public RefinedAbstraction(Implementor imp) { 5 super(imp); 6 } 7 8 //修正父類行為 9 @Override 10 public void request() { 11 super.request(); 12 super.getImp().doAnything(); 13 } 14 15 }
5. Client客戶端
1 public class Client { 2 3 public static void main(String[] args) { 4 Implementor imp = new ConcreteImplementorA(); 5 Abstraction abs = new RefinedAbstraction(imp); 6 abs.request(); 7 } 8 9 }
運行結果如下:
二、橋接模式的應用
1. 何時使用
- 系統可能有多個角度分類,每一種角度都可能變化時
2. 方法
- 把這種角度分類分離出來,讓它們單獨變化,減少它們之間的耦合(合成/聚合復用原則)
3. 優點
- 抽象和實現分離。橋梁模式完全是為了解決繼承的缺點而提出的設計模式
- 優秀的擴展能力
- 實現細節對客戶透明。客戶不用關心細節的實現,它已經由抽象層通過聚合關系完成了封裝
4. 缺點
- 會增加系統的理解與設計難度。由於聚合關聯關系建立在抽象層,要求開發者針對抽象進行設計與編程
5. 使用場景
- 不希望或不適用使用繼承的場景
- 接口或抽象類不穩定的場景
- 重用性要求較高的場景
6. 應用實例
- 開關。我們可以看到的開關是抽象的,不用管里面具體怎么實現
- 手機品牌與手機軟件。兩者間有一條聚合線,一個手機品牌可以有多個手機軟件
7. 注意事項
- 不要一涉及繼承就考慮該模式,盡可能把變化的因素封裝到最細、最小的邏輯單元中,避免風險擴散
- 當發現類的繼承有n層時,可以考慮使用該模式
三、橋接模式的實現
下面我們舉一個例子,就拿上面說的手機品牌與手機軟件為例,我們可以讓手機既可以按照手機品牌來分類,也可以按手機軟件來分類。由於實現的方式有多種,橋接模式的核心意圖就是把這些實現獨立出來,讓它們各自地變化,這就使得沒中實現的變化不會影響其他實現,從而達到應對變化的目的。
UML圖如下:
1. 手機品牌抽象類
橋梁的一頭。
1 public abstract class HandsetBrand { 2 3 protected HandsetSoft soft; 4 5 //設置手機軟件 6 public void setHandsetSoft(HandsetSoft soft) { 7 this.soft = soft; 8 } 9 10 //運行 11 public abstract void run(); 12 13 }
2. 手機軟件抽象類
橋梁的另一頭。兩者通過一條聚合線連接,表示一個手機品牌可以有多個軟件。
1 public abstract class HandsetSoft { 2 3 public abstract void run(); 4 5 }
3. 各類手機品牌
這里寫一個,多余的不再贅述。
1 public class HandsetBrandA extends HandsetBrand { 2 3 @Override 4 public void run() { 5 soft.run(); 6 } 7 8 }
4. 各類手機軟件
有游戲、通訊錄等等,這里寫一個,多余不再贅述。
1 public class HandsetGame extends HandsetSoft { 2 3 @Override 4 public void run() { 5 System.out.println("運行手機游戲"); 6 } 7 8 }
5. Client客戶端
1 public class Client { 2 3 public static void main(String[] args) { 4 HandsetBrand ab; 5 6 //使用A品牌手機 7 ab = new HandsetBrandA(); 8 System.out.println("A品牌手機:"); 9 10 ab.setHandsetSoft(new HandsetGame()); 11 ab.run(); 12 13 ab.setHandsetSoft(new HandsetAddressList()); 14 ab.run(); 15 16 //分隔符 17 System.out.println("---------------"); 18 19 //使用B品牌手機 20 ab = new HandsetBrandB(); 21 System.out.println("B品牌手機:"); 22 23 ab.setHandsetSoft(new HandsetGame()); 24 ab.run(); 25 26 ab.setHandsetSoft(new HandsetAddressList()); 27 ab.run(); 28 } 29 30 }
運行結果如下:
這樣我現在如果想要增加一個功能,比如音樂播放器,那么只有增加這個類就可以了,不會影響到其他任何類,類的個數增加也只是一個;如果是要增加S品牌,只需要增加一個品牌的子類就可以了,個數也是一個,不會影響到其他類。這顯然符合開放-封閉原則。
而這里用到的合成/聚合復用原則是一個很有用處的原則,即優先使用對象的合成或聚合,而不是繼承。究其原因是因為繼承是一種強耦合的結構,父類變,子類就必須變。