1. 前文匯總
2. 橋梁模式
設計模式的最后一篇文章,還是讓我這個拖延症晚期的患者把時間拖到了 2020 年的最后一天。
雖然是最后一篇,但並不是最難的一個模式,比較復雜的兩個模式已經在前面介紹過了,一個是訪問者模式,另一個是解釋器模式。
那么什么是橋梁模式?
2.1 定義
橋梁模式(Bridge Pattern) 也叫做橋接模式, 是一個比較簡單的模式, 其定義如下:Decouple an abstraction from its implementation so that the two can vary independently.(將抽象和實現解耦, 使得兩者可以獨立地變化。 )
橋梁模式的重點是在「解耦」上, 如何讓它們兩者解耦是橋梁模式的重點,
2.2 通用類圖
- Abstraction 抽象化角色:定義出該角色的行為, 同時保存一個對實現化角色的引用, 該角色一般是抽象類
- Implementor 實現化角色:它是接口或者抽象類, 定義角色必需的行為和屬性。
- RefinedAbstraction 修正抽象化角色:實現化角色對抽象化角色進行修正。
- ConcreteImplementor 具體實現化角色:實現接口或抽象類定義的方法和屬性。
橋梁模式中的幾個名詞比較拗口,核心就一句話:抽象角色引用實現角色,或者說抽象角色的部分實現是由實現角色完成的。
2.3 通用代碼
實現化角色:
public interface Implementor {
// 基本方法
void doSomething();
void doAnything();
}
具體實現化角色:
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() {
}
}
抽象化角色:
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;
}
}
具體抽象化角色:
public class RefinedAbstraction extends Abstraction {
// 覆寫構造函數
public RefinedAbstraction(Implementor impl) {
super(impl);
}
// 修正父類的行為
@Override
public void request(){
// 業務處理
super.request();
super.getImpl().doAnything();
}
}
客戶端類:
public class Client {
public static void main(String[] args) {
// 定義一個實現化角色
Implementor imp = new ConcreteImplementor1();
// 定義一個抽象化角色
Abstraction abs = new RefinedAbstraction(imp);
// 執行行文
abs.request();
}
}
2.4 優點
- 抽象和實現分離:橋梁模式完全是為了解決繼承的缺點而提出的設計模式。
- 優秀的擴展能力。
- 實現細節對客戶透明。客戶不用關心細節的實現,它已經由抽象層通過聚合關系完成了封裝。
2.5 缺點
會增加系統的理解與設計難度。由於聚合關聯關系建立在抽象層,要求開發者針對抽象進行設計與編程。
2.6 使用場景
- 不希望或不適用使用繼承的場景。
- 接口或抽象類不穩定的場景。
- 重用性要求較高的場景。
3. 一個簡單的案例
在橋接模式中要把握的很重要的一點就是:類的繼承關系和類的組合/聚合關系,何時應該考慮使用何種關系。是不是在編程過程中一味地使用類的繼承關系就代表這就是面向對象編程了?有時候並不是這樣, Java 的類繼承設計成單繼承模式我想應該就是不想把類的繼承關系搞得過於復雜,實際上我們應該優先使用對象組合/聚合,而不是類繼承。
類繼承不必多說,先來看看何為組合/聚合關系。
聚合體現的是「弱」的擁有關系,比如雁群可以包含大雁,但雁群不是大雁的一部分。組合體現的是「強」的擁有關系,或者體現的是部分與整體的關系,通過一對翅膀組合成大雁,翅膀是部分,大雁是整體。
我們現在天天使用的一個電器:手機,手機有手機品牌和手機軟件等等,每個手機品牌都有多款軟件。
這個案例如果使用繼承關系的話,不管是已手機品牌還是手機軟件作為父類,都會對后續的擴展造成很大的影響,如果我們使用橋梁模式,使用對象的組合/聚合,類圖是下面這樣:
通過UML類結構圖我們可以看到手機品牌和手機軟件成功解耦,新增功能並不影響其手機品牌,新增手機品牌也不會影響到手機軟件,其中的奧秘就在於利用了聚合而不是繼承。
代碼如下:
手機品牌抽象類:
public abstract class HandsetBrand {
protected HandsetSoft soft;
// 設置手機軟件
public void setHandsetSoft(HandsetSoft soft) {
this.soft = soft;
}
// 運行
abstract void run();
}
手機軟件抽象類:
public abstract class HandsetSoft {
abstract void run();
}
各類手機品牌實現類:
public class HandsetBrandA extends HandsetBrand {
@Override
void run() {
super.soft.run();
}
}
public class HandsetBrandB extends HandsetBrand{
@Override
void run() {
super.soft.run();
}
}
手機軟件實現類:
public class HandsetGame extends HandsetSoft{
@Override
void run() {
System.out.println("運行手機游戲");
}
}
public class HandsetAddressList extends HandsetSoft{
@Override
void run() {
System.out.println("運行手機通訊錄");
}
}
客戶端類:
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();
}
}
執行結果如下:
A品牌手機:
運行手機游戲
運行手機通訊錄
B品牌手機:
運行手機游戲
運行手機通訊錄