設想若是要繪製矩形、圓形、橢圓、正方形,咱們至少須要4個形狀類,可是若是繪製的圖形須要具備不一樣的顏色,如紅色、綠色、藍色等,此時至少有以下兩種設計方案:java
• 第一種設計方案是爲每一種形狀都提供一套各類顏色的版本。數據庫
• 第二種設計方案是根據實際須要對形狀和顏色進行組合。編程
對於有兩個變化維度(即兩個變化的緣由)的系統,採用方案二來進行設計系統中類的個數更少,且系統擴展更爲方便。設計方案二便是橋接模式的應用。橋接模式將繼承關係轉換爲關聯關係,從而下降了類與類之間的耦合,減小了代碼編寫量。架構
橋接模式(Bridge Pattern):將抽象部分與它的實現部分分離,使它們均可以獨立地變化。它是一種對象結構型模式,又稱爲柄體(Handle and Body)模式或接口(Interface)模式。dom
橋接模式包含以下角色:ui
• Abstraction:抽象類this
• RefinedAbstraction:擴充抽象類spa
• Implementor:實現類接口操作系統
• ConcreteImplementor:具體實現類設計
理解橋接模式,重點須要理解如何將抽象化(Abstraction)與實現化(Implementation)脫耦,使得兩者能夠獨立地變化。
• 抽象化:抽象化就是忽略一些信息,把不一樣的實體看成一樣的實體對待。在面向對象中,將對象的共同性質抽取出來造成類的過程即爲抽象化的過程。
• 實現化:針對抽象化給出的具體實現,就是實現化,抽象化與實現化是一對互逆的概念,實現化產生的對象比抽象化更具體,是對抽象化事物的進一步具體化的產物。
• 脫耦:脫耦就是將抽象化和實現化之間的耦合解脫開,或者說是將它們之間的強關聯改換成弱關聯,將兩個角色之間的繼承關係改成關聯關係。橋接模式中的所謂脫耦,就是指在一個軟件系統的抽象化和實現化之間使用關聯關係(組合或者聚合關係)而不是繼承關係,從而使二者能夠相對獨立地變化,這就是橋接模式的用意。
典型的實現類接口代碼:
1 public interface Implementor 2 { 3 public void operationImpl(); 4 }
典型的抽象類代碼:
1 public abstract class Abstraction 2 { 3 protected Implementor impl; 4 5 public void setImpl(Implementor impl) 6 { 7 this.impl=impl; 8 } 9 10 public abstract void operation(); 11 }
典型的擴充抽象類代碼:
1 public class RefinedAbstraction extends Abstraction 2 { 3 public void operation() 4 { 5 //代碼 6 impl.operationImpl(); 7 //代碼 8 } 9 }
實例一:模擬毛筆
• 現須要提供大中小3種型號的畫筆,可以繪製5種不一樣顏色,若是使用蠟筆,咱們須要準備3*5=15支蠟筆,也就是說必須準備15個具體的蠟筆類。而若是使用毛筆的話,只須要3種型號的毛筆,外加5個顏料盒,用3+5=8個類就能夠實現15支蠟筆的功能。本實例使用橋接模式來模擬毛筆的使用過程。
實例代碼(JAVA):
1 //抽象類 2 public abstract class Pen 3 { 4 protected Color color; 5 public void setColor(Color color) 6 { 7 this.color=color; 8 } 9 public abstract void draw(String name); 10 } 11 12 //擴充抽象類 13 public class SmallPen extends Pen 14 { 15 public void draw(String name) 16 { 17 String penType="小號毛筆繪製"; 18 this.color.bepaint(penType,name); 19 } 20 } 21 22 //擴充抽象類 23 public class MiddlePen extends Pen 24 { 25 public void draw(String name) 26 { 27 String penType="中號毛筆繪製"; 28 this.color.bepaint(penType,name); 29 } 30 } 31 32 //擴充抽象類 33 public class BigPen extends Pen 34 { 35 public void draw(String name) 36 { 37 String penType="大號毛筆繪製"; 38 this.color.bepaint(penType,name); 39 } 40 } 41 42 //實現類接口 43 public interface Color 44 { 45 void bepaint(String penType,String name); 46 } 47 48 //擴充實現類 49 public class Red implements Color 50 { 51 public void bepaint(String penType,String name) 52 { 53 System.out.println(penType + "紅色的"+ name + "."); 54 } 55 } 56 57 //擴充實現類 58 public class Green implements Color 59 { 60 public void bepaint(String penType,String name) 61 { 62 System.out.println(penType + "綠色的"+ name + "."); 63 } 64 } 65 66 //擴充實現類 67 public class Blue implements Color 68 { 69 public void bepaint(String penType,String name) 70 { 71 System.out.println(penType + "藍色的"+ name + "."); 72 } 73 } 74 75 //擴充實現類 76 public class White implements Color 77 { 78 public void bepaint(String penType,String name) 79 { 80 System.out.println(penType + "白色的"+ name + "."); 81 } 82 } 83 84 //擴充實現類 85 public class Black implements Color 86 { 87 public void bepaint(String penType,String name) 88 { 89 System.out.println(penType + "黑色的"+ name + "."); 90 } 91 } 92 93 //配置文件configPen.xml 94 <?xml version="1.0"?> 95 <config> 96 <className>Blue</className> 97 <className>SmallPen</className> 98 </config> 99 100 //使用java反射建立具體的顏色和畫筆 101 import javax.xml.parsers.*; 102 import org.w3c.dom.*; 103 import org.xml.sax.SAXException; 104 import java.io.*; 105 public class XMLUtilPen 106 { 107 //該方法用於從XML配置文件中提取具體類類名,並返回一個實例對象 108 public static Object getBean(String args) 109 { 110 try 111 { 112 //建立文檔對象 113 DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance(); 114 DocumentBuilder builder = dFactory.newDocumentBuilder(); 115 Document doc; 116 doc = builder.parse(new File("configPen.xml")); 117 NodeList nl=null; 118 Node classNode=null; 119 String cName=null; 120 nl = doc.getElementsByTagName("className"); 121 122 if(args.equals("color")) 123 { 124 //獲取包含類名的文本節點 125 classNode=nl.item(0).getFirstChild(); 126 127 } 128 else if(args.equals("pen")) 129 { 130 //獲取包含類名的文本節點 131 classNode=nl.item(1).getFirstChild(); 132 } 133 134 cName=classNode.getNodeValue(); 135 //經過類名生成實例對象並將其返回 136 Class c=Class.forName(cName); 137 Object obj=c.newInstance(); 138 return obj; 139 } 140 catch(Exception e) 141 { 142 e.printStackTrace(); 143 return null; 144 } 145 } 146 } 147 148 //客戶端 149 public class Client 150 { 151 public static void main(String a[]) 152 { 153 Color color; 154 Pen pen; 155 156 color=(Color)XMLUtilPen.getBean("color"); 157 pen=(Pen)XMLUtilPen.getBean("pen"); 158 159 pen.setColor(color); 160 pen.draw("鮮花"); 161 } 162 }
實例二:跨平臺視頻播放器
• 若是須要開發一個跨平臺視頻播放器,能夠在不一樣操做系統平臺(如Windows、Linux、Unix等)上播放多種格式的視頻文件,常見的視頻格式包括MPEG、RMVB、AVI、WMV等。現使用橋接模式設計該播放器。
優勢
• 分離抽象接口及其實現部分。
• 橋接模式有時相似於多繼承方案,可是多繼承方案違背了類的單一職責原則(即一個類只有一個變化的緣由),複用性比較差,並且多繼承結構中類的個數很是龐大,橋接模式是比多繼承方案更好的解決方法。
• 橋接模式提升了系統的可擴充性,在兩個變化維度中任意擴展一個維度,都不須要修改原有系統。
• 實現細節對客戶透明,能夠對用戶隱藏實現細節。
缺點
• 橋接模式的引入會增長系統的理解與設計難度,因爲聚合關聯關係創建在抽象層,要求開發者針對抽象進行設計與編程。
• 橋接模式要求正確識別出系統中兩個獨立變化的維度,所以其使用範圍具備必定的侷限性。
在如下狀況下可使用橋接模式:
• 若是一個系統須要在構件的抽象化角色和具體化角色之間增長更多的靈活性,避免在兩個層次之間創建靜態的繼承聯繫,經過橋接模式可使它們在抽象層創建一個關聯關係。
• 抽象化角色和實現化角色能夠以繼承的方式獨立擴展而互不影響,在程序運行時能夠動態將一個抽象化子類的對象和一個實現化子類的對象進行組合,即系統須要對抽象化角色和實現化角色進行動態耦合。
• 一個類存在兩個獨立變化的維度,且這兩個維度都須要進行擴展。
• 雖然在系統中使用繼承是沒有問題的,可是因爲抽象化角色和具體化角色須要獨立變化,設計要求須要獨立管理這二者。
• 對於那些不但願使用繼承或由於多層次繼承致使系統類的個數急劇增長的系統,橋接模式尤其適用。
(1) Java語言經過Java虛擬機實現了平臺的無關性。
(2) 一個 Java桌面軟件老是帶有所在操做系統的視感(LookAndFeel),若是一個Java軟件是在Unix系統上開發的,那麼開發人員看到的是Motif用戶界面的視感;在Windows上面使用這個系統的用戶看到的是Windows用戶界面的視感;而一個在Macintosh上面使用的用戶看到的則是Macintosh用戶界面的視感,Java語言是經過所謂的Peer架構作到這一點的。Java爲AWT中的每個GUI構件都提供了一個Peer構件,在AWT中的Peer架構就使用了橋接模式。
(3) JDBC驅動程序也是橋接模式的應用之一。使用JDBC驅動程序的應用系統就是抽象角色,而所使用的數據庫是實現角色。一個JDBC驅動程序能夠動態地將一個特定類型的數據庫與一個Java應用程序綁定在一塊兒,從而實現抽象角色與實現角色的動態耦合。
適配器模式與橋接模式的聯用
• 橋接模式和適配器模式用於設計的不一樣階段,橋接模式用於系統的初步設計,對於存在兩個獨立變化維度的類能夠將其分爲抽象化和實現化兩個角色,使它們能夠分別進行變化;而在初步設計完成以後,當發現系統與已有類沒法協同工做時,能夠採用適配器模式。但有時候在設計初期也須要考慮適配器模式,特別是那些涉及到大量第三方應用接口的狀況。