設計原則是指導我們程式碼設計的一些經驗總結,也就是“心法”;物件導向就是我們的“武器”;設計模式就是“招式”。
以心法為基礎,以武器運用招式應對複雜的程式設計問題。
在現實生活中,某些類具有兩個或多個維度的變化,比如,圖形既可以按形狀分,又可以按顏色分;手機既可以按品牌分,又可以按配置分;還有支援不同平臺和不同檔案格式的視訊播放器等等。你看,如果兩個維度上分別有m,n種型別,那麼,就會有m*n種組合,如果有多個維度上的變化,那麼會有更多種組合。如果使用繼承方式,不但對應的子類會非常多,而且會導致擴充套件和修改都很困難。
這時候,橋接(橋樑)模式就派上用場啦~
橋接模式
將抽象和實現解耦,使得兩者可以獨立地變化。
抽象類或介面和實現解耦?獨立地變化?what are you saying?不應該是“抽象”不變,“實現”可以隨便改變嘛?
其實,橋接模式中有兩種角色,分別是抽象化角色和實現化角色,實現化角色並不是直接實現了抽象化角色定義的介面,而是提供更底層的方法,使抽象化角色可以基於實現化角色封裝出自己的介面實現。
所謂“解耦”, 我們知道,橋,用來將河的兩岸連通起來,而我們這裡的橋樑模式,就是用來將兩個獨立的結構聯絡起來,這兩個被聯絡起來的結構可以獨立的變化。
橋樑模式的重點是在“解耦”上。
比如,我們要畫一個有顏色、有形狀的圖形,那麼說明這個具體的圖形在形狀和顏色上都會變化的,如下圖所示:
如果我們按形狀來分類,那麼它的結構圖如下:
具體程式碼如下:
現有長方形和正方形,它們可以抽象出形狀這個父類,然後它們對應兩個子類。
1 abstract class Shape { 2 abstract void draw(); 3 } 4 5 public class Rectangle extends Shape { 6 @Override void draw() { System.out.println("繪製長方形"); } 7 } 8 9 public class Square extends Shape { 10 @Override void draw() { System.out.println("繪製正方形"); } 11 }
如果加入了顏色,有紅色和藍色,跟形狀組合成下面4種子類:
1 public class RedRectangle extends Rectangle { 2 @Override void draw() { System.out.println("繪製紅色長方形"); } 3 } 4 5 public class RedSquare extends Square { 6 @Override void draw() { System.out.println("繪製紅色正方形"); } 7 } 8 9 public class BlueRectangle extends Rectangle { 10 @Override void draw() { System.out.println("繪製藍色長方形"); } 11 } 12 13 public class BlueSquare extends Square { 14 @Override void draw() { System.out.println("繪製藍色正方形"); } 15 }
你看,現在有兩種形狀,分別是長方形和正方形。同時也有兩種顏色,那麼就會有2 * 2種組合,假如未來有一天,我們要新增一個圓形,那麼就會有3 * 2種組合,類的個數暴漲。或者如果不要現在不要長方形了,但是要圓形,那麼,改了“長方形”類,它的子類“紅色長方形”和“藍色長方形”都要跟著改。這完全違背了開-閉原則。
那麼,如果按顏色分類會怎樣呢?我們來看一下實現的結構圖:
你看,這個結構體系還是一樣的,不管是按形狀還是按顏色分類,整個繼承體系都會很複雜,很龐大,牽一髮而動全身。
現在,我們這個業務場景有兩個角度分類,每個角度都有可能變化,那麼就把這種多角度分離出來(使用關聯關係),讓它們獨立變化,減少它們之間的耦合。這就是“橋接模式”,我們先來認識一下橋接模式的結構圖是怎樣的:
設計的角色:
-
Abstraction:定義抽象介面,擁有一個Implementor型別的物件引用;
-
RefinedAbstraction:擴充套件Abstraction中的介面定義;
-
Implementor:是具體實現的介面,Implementor和RefinedAbstraction介面並不一定完全一致,實際上這兩個介面可以完全不一樣,Implementor提供具體操作方法,而Abstraction提供更高層次的呼叫;
-
ConcreteImplementor:實現Implementor介面,給出具體實現。
好啦,我們回到畫帶有顏色的圖形的例子中,看看使用橋接模式後,它的結構圖是怎樣的:
你看,是不是清晰很多了,左右兩邊各有一塊“大陸”,中間使用一座“橋樑”,把它們聯絡起來。未來,在每塊“大陸”內部不管做刪減、還是新增,都不會影響另一塊“大陸”的建設。這不就是“組合優於繼承”的好處嘛。
我們來看一下程式碼實現:
Implementor(這裡是形狀的介面)
1 public interface Shape { 2 public void draw(); 3 }
ConcreteImplementor(具體的形狀:長方形和正方形)
1 public class Square implements Shape { 2 @Override 3 public void draw() { 4 System.out.println("繪製正方形"); 5 } 6 } 7 8 public class Rectangle implements Shape { 9 @Override 10 public void draw() { 11 System.out.println("繪製長方形"); 12 } 13 } 14 15 // 新增一個圓形,邏輯是一樣的,直接新增一個實現類...很靈活
Abstraction(顏色抽象類,這裡包含了一個Implementor)
1 public abstract class Color { 2 // 組合Shape介面 3 protected Shape shape; 4 5 public Color(Shape shape) { 6 this.shape = shape; 7 } 8 9 public abstract void coloring(); 10 }
RefinedAbstraction(具體的顏色類)
1 public class BlueShape extends Color { 2 public BlueShape(Shape shape) { 3 super(shape); 4 } 5 6 @Override 7 public void coloring() { 8 super.shape.draw(); 9 System.out.println("給該圖形染上藍色"); 10 } 11 } 12 13 public class RedShape extends Color { 14 public RedShape(Shape shape) { 15 super(shape); 16 } 17 18 @Override 19 public void coloring() { 20 super.shape.draw(); 21 System.out.println("給該圖形染上紅色"); 22 } 23 }
現在,我們來畫一個藍色的正方形:
1 Shape square = new Square(); 2 Color blueSquare = new BlueShape(square); 3 blueSquare.coloring();
如果你要畫紅色的長方形,也是一樣的邏輯:
1 Shape rectangle = new Rectangle(); 2 Color redRectangle = new BlueShape(rectangle); 3 redRectangle.coloring();
繼承一定“一無是處”嗎?
我們知道,繼承是強侵入的,父類有一個方法,子類也必須有這個方法,這是不可選擇的,這樣就會帶來擴充套件性的問題。比如,Father類有一個方法A,Son繼承了這個方法,然後GrandSon也繼承了這個方法。那麼如果,未來有一天,Son要重寫Father的這個方法,可以隨便重寫嘛?肯定不能的,因為GrandSon還在用從Father繼承過來的方法A,如果Son重寫了,那就要修改Son和GrandSon之間的關係,這樣的改動,風險就太大了。
但並不是說繼承並不好,只是我們需要根據具體的應用場景,具體分析。比如,對於比較明確不發生變化的,或者確定是“is-a”的關係時,就通過繼承來完成;若不能確定是否會發生變化的,那就認為是會發生變化,則考慮使用橋樑模式來解決。具體問題,具體分析!
橋樑模式的優點
-
抽象和實現分離
這也是橋樑模式的主要特點,它完全是為了解決繼承的缺點而提出的設計模式。在該模式下,實現可以不受抽象的約束,不用再繫結在一個固定的抽象層次上。
-
優秀的擴充能力
想增加實現?沒問題,不改變已有程式碼!想增加抽象?也沒問題,也不改變已有程式碼!只要對外暴露的介面層允許這樣的變化,我們已經把變化的可能性減到最小。
-
實現細節對客戶透明
客戶不用關心細節的實現,它已經由抽象層通過聚合關係完成了封裝。
橋樑模式的缺點
-
增加了系統的理解和設計難度。
-
需要正確地識別出系統中兩個或多個獨立變化的維度。
橋樑模式的應用場景
-
不希望或不適用使用繼承的場景
例如,繼承層次過度、無法更細化設計顆粒度等場景,需要考慮用橋樑模式。
-
介面或抽象類不穩定的場景
明知道介面不穩定還想通過實現或繼承來實現業務需求,那是得不償失的,也是比較失敗的做法。
-
重用性要求較高的場景
設計的顆粒度越細,則被重用的可能性就越大,而採用繼承則受父類的限制,不可能出現太細的顆粒度。
總結
橋接模式使用物件間的組合關係解耦了抽象和實現之間固有的繫結關係,使得抽象和實現可以沿著各自的維度來變化。
參考
《設計模式之禪》
https://www.runoob.com/w3cnote/bridge-pattern2.html