1、意圖:
將抽象部分(抽象接口)與它的實現部分(代碼實現)分離,使它們都可以獨立地變化。
理解:抽象部分是對外展現的接口(api),而實現部分是針對抽象接口提供的不同版本的功能實現,使兩者獨立變化指兩者可以在各自的維度上自由變化,而不會產生太大的影響。如可以在api中添加新的接口,而不影響具體實現部分;可以在實現部分針對特定接口添加新的實現方式,而不影響抽象接口定義。
橋接模式將類的繼承關系轉變為類的聚合關系(見下圖)。對於抽象接口及其實現,Java中通常的實現方式是通過抽象類的繼承或接口的實現,但是橋接模式,將這種抽象接口與實現之間的關系變成了聚合關系。
2、橋接模式(也稱:橋梁模式)類圖
角色:
- Abstraction:定義抽象接口,對外提供api;自身維護一個Implementor(橋)的引用,使用Implementor的操作來完成自身接口功能,如operation方法通過impl.doOperation()來實現;
- RefinedAbstraction:擴展Abstraction的抽象接口;
- Implementor:架在抽象接口與不同實現方式之間的橋,且自身的接口不必與Abstraction完全相同;
- ConcreteImplementorA:實現Implementor(橋)的接口,針對Implementor(橋)中的接口doOperation提供特色化的實現;
- Client:用戶類。
協作:
- Client調用Abstraction提供的接口,而Abstraction將Client的調用通過Implementor來實現。
3、JDBC與橋接:
JDBC是橋接模式的典型實現。
先看下類圖:
通常使用JDBC連接數據庫時,會使用如下代碼:
1 Class.forName("數據庫類驅動器"); 2 Connection conn = DriverManager.getConnection("數據庫url", "用戶名", "密碼"); 3 //.................
針對不同的數據庫,JDBC都可以通過java.sql.DriverManager類的靜態方法getConnection(數據庫url, 用戶名, 密碼)來獲取數據庫的連接。JDBC通過DriverManager對外提供了操作數據庫的統一接口getConnection,通過該方法可以獲取不同數據庫的連接,並且通過Connection類提供的接口來進行數據的查詢操作。
JDBC為不同的數據庫操作提供了相同的接口,但是JDBC本身並沒有針對每種數據庫提供一套具體實現代碼,而是通過接口java.sql.Driver的connect方法連接到了不同的數據庫實現。
1 public interface Driver 2 { 3 4 public abstract Connection connect(String s, Properties properties) throws SQLException; 5 //其他方法 6 7 }
在JDBC中,針對不同數據庫提供的統一的操作接口通過java.sql.Driver(橋)連接到了不同的數據庫實現。如連接mysql數據庫。
1 package com.mysql.jdbc; 2 3 public class NonRegisteringDriver implements java.sql.Driver //對java.sql.Driver接口提供了實現 4 { 5 6 public Connection connect(String url, Properties info) 7 throws SQLException 8 { 9 //實現 10 } 11 12 //其他方法 13 }
Java在連接MySQL時需要使用mysql-connector-java.jar,mysql-connector-java.jar包提供了對MySQL數據庫操作的具體實現,並通過接口Driver連接到了JDBC統一的api。
4、JDBC中橋接模式具體如何實現?
既然,針對不同的數據庫,通過DriverManger.getConnection()可以獲得相同的Connection接口,那先看DriverManager的源碼:
1 public class DriverManager 2 { 3 private static final CopyOnWriteArrayList registeredDrivers = new CopyOnWriteArrayList(); //存放DriverInfo的鏈表 4 5 public static synchronized void registerDriver(Driver driver) 6 throws SQLException 7 { 8 if(driver != null) 9 registeredDrivers.addIfAbsent(new DriverInfo(driver)); //向鏈表中添加DriverInfo實例,DriverInfo封裝了Driver 10 else 11 throw new NullPointerException(); 12 println((new StringBuilder()).append("registerDriver: ").append(driver).toString()); 13 } 14 15 private static Connection getConnection(String s, Properties properties, Class class1) 16 throws SQLException 17 { 18 //..... 19 Iterator iterator = registeredDrivers.iterator(); //遍歷registeredDrivers表 20 do 21 { 22 if(!iterator.hasNext()) 23 break; 24 DriverInfo driverinfo = (DriverInfo)iterator.next(); 25 if(isDriverAllowed(driverinfo.driver, classloader)) 26 try 27 { 28 Connection connection = driverinfo.driver.connect(s, properties); //調用Driver接口提供的connect方法來獲取Connection對象 29 if(connection != null) 30 { 31 return connection; 32 } 33 } 34 catch(SQLException sqlexception1) 35 { 36 if(sqlexception == null) 37 sqlexception = sqlexception1; 38 } 39 } while(true); 40 } 41 42 //其他方法 43 44 } 45 46
從DriverManager.getConnection()源碼可見,方法中遍歷了包含DriverInfo實例的表registeredDrivers,通過表中實例driverinfo來獲取封裝的java.sql.Driver類型的實例,並調用java.sql.Driver接口的connect方法獲取到Connection。
【注:DriverInfo是Driver的封裝類。由DriverInfo源碼可見。

1 class DriverInfo 2 { 3 4 DriverInfo(Driver driver1) 5 { 6 driver = driver1; 7 } 8 9 public boolean equals(Object obj) 10 { 11 return (obj instanceof DriverInfo) && driver == ((DriverInfo)obj).driver; 12 } 13 14 public int hashCode() 15 { 16 return driver.hashCode(); 17 } 18 19 public String toString() 20 { 21 return (new StringBuilder()).append("driver[className=").append(driver).append("]").toString(); 22 } 23 24 final Driver driver; 25 }
】
那么,Driver實例是何時注入到DriverManager類的registeredDrivers中的呢?以mysql為例,在每次使用JDBC連接mysql時,都會有下面的調用:
1 Class.forName("com.mysql.jdbc.Driver");
該行代碼通過反射加載了com.mysql.jdbc.Driver類(com.mysql.jdbc.Driver類在mysql-connector-java.jar中,而mysql-connector-java.jar是JDBC連接MySQL的jar包),查看com.mysql.jdbc.Driver類:
1 package com.mysql.jdbc; 2 3 public class Driver extends NonRegisteringDriver 4 implements java.sql.Driver 5 { 6 7 public Driver() 8 throws SQLException 9 { 10 } 11 12 static 13 { 14 try 15 { 16 DriverManager.registerDriver(new Driver()); 17 } 18 catch(SQLException E) 19 { 20 throw new RuntimeException("Can't register driver!"); 21 } 22 } 23 }
在com.mysql.jdbc.Driver的源碼中可以看到在加載com.mysql.jdbc.Driver類時,通過類中的靜態域中的紅色代碼,會調用DriverManager的registerDriver方法將當前MySQL的驅動類實例注入到DriverManager的registeredDrivers中。
通過整個代碼調用,展示了橋接模式在JDBC中是如何運用的。
5、適用性:
- 當不希望抽象接口和實現部分采用固定的綁定關系時;
6、特點:
- 橋接模式良好地實現了開閉原則:通常應用橋接模式的地方,抽象接口和具體實現部分都是可以變化的,且抽象接口與實現耦合度低;
- 橋接模式將類繼承關系轉換成了對象組合關系,實現了類的復用,減少了類的個數。