「補課」進行時:設計模式(22)——橋樑模式
阿新 • • 發佈:2021-01-01
![](https://cdn.geekdigging.com/DesignPatterns/java_design_pattern.jpg)
## 1. 前文彙總
[「補課」進行時:設計模式系列](https://www.geekdigging.com/category/%e8%ae%be%e8%ae%a1%e6%a8%a1%e5%bc%8f/)
## 2. 橋樑模式
設計模式的最後一篇文章,還是讓我這個拖延症晚期的患者把時間拖到了 2020 年的最後一天。
雖然是最後一篇,但並不是最難的一個模式,比較複雜的兩個模式已經在前面介紹過了,一個是訪問者模式,另一個是直譯器模式。
那麼什麼是橋樑模式?
### 2.1 定義
橋樑模式(Bridge Pattern) 也叫做橋接模式, 是一個比較簡單的模式, 其定義如下:Decouple an abstraction from its implementation so that the two can vary independently.(將抽象和實現解耦, 使得兩者可以獨立地變化。 )
橋樑模式的重點是在「解耦」上, 如何讓它們兩者解耦是橋樑模式的重點,
### 2.2 通用類圖
![](https://cdn.geekdigging.com/DesignPatterns/22/Bridge_UML.png)
- Abstraction 抽象化角色:定義出該角色的行為, 同時儲存一個對實現化角色的引用, 該角色一般是抽象類
- Implementor 實現化角色:它是介面或者抽象類, 定義角色必需的行為和屬性。
- RefinedAbstraction 修正抽象化角色:實現化角色對抽象化角色進行修正。
- ConcreteImplementor 具體實現化角色:實現介面或抽象類定義的方法和屬性。
橋樑模式中的幾個名詞比較拗口,核心就一句話:抽象角色引用實現角色,或者說抽象角色的部分實現是由實現角色完成的。
### 2.3 通用程式碼
實現化角色:
```java
public interface Implementor {
// 基本方法
void doSomething();
void doAnything();
}
```
具體實現化角色:
```java
public class ConcreteImplementor1 implements Implementor {
@Override
public void doSomething() {
}
@Override
public void doAnything() {
}
}
public class ConcreteImplementor2 implements Implementor {
@Override
public void doSomething() {
}
@Override
public void doAnything() {
}
}
```
抽象化角色:
```java
public abstract class Abstraction {
// 定義對實現化角色的引用
private Implementor impl;
// 約束子類必須實現該建構函式
public Abstraction(Implementor impl){
this.impl = impl;
}
// 自身的行為和屬性
public void request(){
this.impl.doSomething();
}
// 獲得實現化角色
public Implementor getImpl(){
return impl;
}
}
```
具體抽象化角色:
```java
public class RefinedAbstraction extends Abstraction {
// 覆寫建構函式
public RefinedAbstraction(Implementor impl) {
super(impl);
}
// 修正父類的行為
@Override
public void request(){
// 業務處理
super.request();
super.getImpl().doAnything();
}
}
```
客戶端類:
```java
public class Client {
public static void main(String[] args) {
// 定義一個實現化角色
Implementor imp = new ConcreteImplementor1();
// 定義一個抽象化角色
Abstraction abs = new RefinedAbstraction(imp);
// 執行行文
abs.request();
}
}
```
### 2.4 優點
1. 抽象和實現分離:橋樑模式完全是為了解決繼承的缺點而提出的設計模式。
2. 優秀的擴充套件能力。
3. 實現細節對客戶透明。客戶不用關心細節的實現,它已經由抽象層通過聚合關係完成了封裝。
### 2.5 缺點
會增加系統的理解與設計難度。由於聚合關聯關係建立在抽象層,要求開發者針對抽象進行設計與程式設計。
### 2.6 使用場景
- 不希望或不適用使用繼承的場景。
- 介面或抽象類不穩定的場景。
- 重用性要求較高的場景。
## 3. 一個簡單的案例
在橋接模式中要把握的很重要的一點就是:類的繼承關係和類的組合/聚合關係,何時應該考慮使用何種關係。是不是在程式設計過程中一味地使用類的繼承關係就代表這就是面向物件程式設計了?有時候並不是這樣, Java 的類繼承設計成單繼承模式我想應該就是不想把類的繼承關係搞得過於複雜,實際上我們應該優先使用物件組合/聚合,而不是類繼承。
類繼承不必多說,先來看看何為組合/聚合關係。
![](https://cdn.geekdigging.com/DesignPatterns/22/630246-20161007214233020-778214060.png)
聚合體現的是「弱」的擁有關係,比如雁群可以包含大雁,但雁群不是大雁的一部分。組合體現的是「強」的擁有關係,或者體現的是部分與整體的關係,通過一對翅膀組合成大雁,翅膀是部分,大雁是整體。
我們現在天天使用的一個電器:手機,手機有手機品牌和手機軟體等等,每個手機品牌都有多款軟體。
這個案例如果使用繼承關係的話,不管是已手機品牌還是手機軟體作為父類,都會對後續的擴充套件造成很大的影響,如果我們使用橋樑模式,使用物件的組合/聚合,類圖是下面這樣:
![](https://cdn.geekdigging.com/DesignPatterns/22/Bridge_Demo_UML.png)
通過UML類結構圖我們可以看到手機品牌和手機軟體成功解耦,新增功能並不影響其手機品牌,新增手機品牌也不會影響到手機軟體,其中的奧祕就在於利用了聚合而不是繼承。
程式碼如下:
手機品牌抽象類:
```java
public abstract class HandsetBrand {
protected HandsetSoft soft;
// 設定手機軟體
public void setHandsetSoft(HandsetSoft soft) {
this.soft = soft;
}
// 執行
abstract void run();
}
```
手機軟體抽象類:
```java
public abstract class HandsetSoft {
abstract void run();
}
```
各類手機品牌實現類:
```java
public class HandsetBrandA extends HandsetBrand {
@Override
void run() {
super.soft.run();
}
}
public class HandsetBrandB extends HandsetBrand{
@Override
void run() {
super.soft.run();
}
}
```
手機軟體實現類:
```java
public class HandsetGame extends HandsetSoft{
@Override
void run() {
System.out.println("執行手機遊戲");
}
}
public class HandsetAddressList extends HandsetSoft{
@Override
void run() {
System.out.println("執行手機通訊錄");
}
}
```
客戶端類:
```java
public class Client {
public static void main(String[] args) {
HandsetBrand ab;
// 使用 A 品牌手機
ab = new HandsetBrandA();
System.out.println("A品牌手機:");
ab.setHandsetSoft(new HandsetGame());
ab.run();
ab.setHandsetSoft(new HandsetAddressList());
ab.run();
// 使用 B 品牌手機
ab = new HandsetBrandB();
System.out.println("B品牌手機:");
ab.setHandsetSoft(new HandsetGame());
ab.run();
ab.setHandsetSoft(new HandsetAddressList());
ab.run();
}
}
```
執行結果如下:
```java
A品牌手機:
執行手機遊戲
執行手機通訊錄
B品牌手機:
執行手機遊戲
執行手機通