設計模式--結構性模式--橋接模式(Bridge模式)
在現實生活中,某些類具有兩個或多個維度的變化,如圖形既可按形狀分,又可按顏色分。如何設計類似於 Photoshop 這樣的軟體,能畫不同形狀和不同顏色的圖形呢?如果用繼承方式,m 種形狀和 n 種顏色的圖形就有 m×n 種,不但對應的子類很多,而且擴充套件困難。
當然,這樣的例子還有很多,如不同顏色和字型的文字、不同品牌和功率的汽車、不同性別和職業的男女、支援不同平臺和不同檔案格式的媒體播放器等。如果用橋接模式就能很好地解決這些問題。
橋接模式的定義與特點
橋接(Bridge)模式的定義如下:將抽象與實現分離,使它們可以獨立變化。它是用組合關係代替繼承關係來實現,從而降低了抽象和實現這兩個可變維度的耦合度。
通過上面的講解,我們能很好的感覺到橋接模式遵循了里氏替換原則和依賴倒置原則
橋接(Bridge)模式的優點是:
- 抽象與實現分離,擴充套件能力強
- 符合開閉原則
- 符合合成複用原則
- 其實現細節對客戶透明
缺點是:由於聚合關係建立在抽象層,要求開發者針對抽象化進行設計與程式設計,能正確地識別出系統中兩個獨立變化的維度,這增加了系統的理解與設計難度。
橋接模式的結構與實現
可以將抽象化部分與實現化部分分開,取消二者的繼承關係,改用組合關係。
1. 模式的結構
橋接(Bridge)模式包含以下主要角色。
- 抽象化(Abstraction)角色:定義抽象類,幷包含一個對實現化物件的引用。
- 擴充套件抽象化(Refined Abstraction)角色:是抽象化角色的子類,實現父類中的業務方法,並通過組合關係
- 實現化(Implementor)角色:定義實現化角色的介面,供擴充套件抽象化角色呼叫。
- 具體實現化(Concrete Implementor)角色:給出實現化角色介面的具體實現。
其結構圖如圖 1 所示。
2. 模式的實現
橋接模式的程式碼如下:
package bridge; public class BridgeTest { public static void main(String[] args) { Implementor imple = new ConcreteImplementorA(); Abstraction abs= new RefinedAbstraction(imple); abs.Operation(); } } //實現化角色 interface Implementor { public void OperationImpl(); } //具體實現化角色 class ConcreteImplementorA implements Implementor { public void OperationImpl() { System.out.println("具體實現化(Concrete Implementor)角色被訪問"); } } //抽象化角色 abstract class Abstraction { protected Implementor imple; protected Abstraction(Implementor imple) { this.imple = imple; } public abstract void Operation(); } //擴充套件抽象化角色 class RefinedAbstraction extends Abstraction { protected RefinedAbstraction(Implementor imple) { super(imple); } public void Operation() { System.out.println("擴充套件抽象化(Refined Abstraction)角色被訪問"); imple.OperationImpl(); } }
程式的執行結果如下:
擴充套件抽象化(Refined Abstraction)角色被訪問
具體實現化(Concrete Implementor)角色被訪問
橋接模式的應用例項
【例1】用橋接(Bridge)模式模擬女士皮包的選購。
分析:女士皮包有很多種,可以按用途分、按皮質分、按品牌分、按顏色分、按大小分等,存在多個維度的變化,所以採用橋接模式來實現女士皮包的選購比較合適。
本例項按用途分可選錢包(Wallet)和挎包(HandBag),按顏色分可選黃色(Yellow)和紅色(Red)。可以按兩個維度定義為顏色類和包類。(點此下載本例項所要顯示的包的圖片)。
顏色類(Color)是一個維度,定義為實現化角色,它有兩個具體實現化角色:黃色和紅色,通過 getColor() 方法可以選擇顏色;包類(Bag)是另一個維度,定義為抽象化角色,它有兩個擴充套件抽象化角色:挎包和錢包,它包含了顏色類物件,通過 getName() 方法可以選擇相關顏色的挎包和錢包。
客戶類通過 ReadXML 類從 XML 配置檔案中獲取包資訊(點此下載 XML 配置檔案),並把選到的產品通過窗體顯示出現,圖 2 所示是其結構圖。
程式程式碼如下:
package bridge; import org.w3c.dom.NodeList; import javax.swing.*; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import java.awt.*; public class BagManage { public static void main(String[] args) { Color color; Bag bag; color = (Color) ReadXML.getObject("color"); bag = (Bag) ReadXML.getObject("bag"); bag.setColor(color); String name = bag.getName(); show(name); } public static void show(String name) { JFrame jf = new JFrame("橋接模式測試"); Container contentPane = jf.getContentPane(); JPanel p = new JPanel(); JLabel l = new JLabel(new ImageIcon("src/bridge/" + name + ".jpg")); p.setLayout(new GridLayout(1, 1)); p.setBorder(BorderFactory.createTitledBorder("女士皮包")); p.add(l); contentPane.add(p, BorderLayout.CENTER); jf.pack(); jf.setVisible(true); jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } } //實現化角色:顏色 interface Color { String getColor(); } //具體實現化角色:黃色 class Yellow implements Color { public String getColor() { return "yellow"; } } //具體實現化角色:紅色 class Red implements Color { public String getColor() { return "red"; } } //抽象化角色:包 abstract class Bag { protected Color color; public void setColor(Color color) { this.color = color; } public abstract String getName(); } //擴充套件抽象化角色:挎包 class HandBag extends Bag { public String getName() { return color.getColor() + "HandBag"; } } //擴充套件抽象化角色:錢包 class Wallet extends Bag { public String getName() { return color.getColor() + "Wallet"; } } package bridge; import javax.xml.parsers.*; import org.w3c.dom.*; import java.io.*; class ReadXML { public static Object getObject(String args) { try { DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = dFactory.newDocumentBuilder(); Document doc; doc = builder.parse(new File("src/bridge/config.xml")); NodeList nl = doc.getElementsByTagName("className"); Node classNode = null; if (args.equals("color")) { classNode = nl.item(0).getFirstChild(); } else if (args.equals("bag")) { classNode = nl.item(1).getFirstChild(); } String cName = "bridge." + classNode.getNodeValue(); Class<?> c = Class.forName(cName); Object obj = c.newInstance(); return obj; } catch (Exception e) { e.printStackTrace(); return null; } } }
程式的執行結果如圖 3 所示。
如果將 XML 配置檔案按如下修改:
<?xml version="1.0" encoding="UTF-8"?> <config> <className>Red</className> <className>Wallet</className> </config>
則程式的執行結果如圖 4 所示。
橋接模式的應用場景
當一個類內部具備兩種或多種變化維度時,使用橋接模式可以解耦這些變化的維度,使高層程式碼架構穩定。
橋接模式通常適用於以下場景。
- 當一個類存在兩個獨立變化的維度,且這兩個維度都需要進行擴充套件時。
- 當一個系統不希望使用繼承或因為多層次繼承導致系統類的個數急劇增加時。
- 當一個系統需要在構件的抽象化角色和具體化角色之間增加更多的靈活性時。
橋接模式的一個常見使用場景就是替換繼承。我們知道,繼承擁有很多優點,比如,抽象、封裝、多型等,父類封裝共性,子類實現特性。繼承可以很好的實現程式碼複用(封裝)的功能,但這也是繼承的一大缺點。
因為父類擁有的方法,子類也會繼承得到,無論子類需不需要,這說明繼承具備強侵入性(父類程式碼侵入子類),同時會導致子類臃腫。因此,在設計模式中,有一個原則為優先使用組合/聚合,而不是繼承。
很多時候,我們分不清該使用繼承還是組合/聚合或其他方式等,其實可以從現實語義進行思考。因為軟體最終還是提供給現實生活中的人使用的,是服務於人類社會的,軟體是具備現實場景的。當我們從純程式碼角度無法看清問題時,現實角度可能會提供更加開闊的思路。
橋接模式模式的擴充套件
在軟體開發中,有時橋接(Bridge)模式可與介面卡模式聯合使用。當橋接(Bridge)模式的實現化角色的介面與現有類的介面不一致時,可以在二者中間定義一個介面卡將二者連線起來,其具體結構圖如圖 5 所示。