摘要:橋接模式屬于結構型模式的一種,用于把抽象化與實現化解耦,使得二者可以獨立變化,它通過提供抽象化和實現化之間的橋接結構,來實現二者的解耦。相關模式裝飾模式與橋接模式在一定程度上都是為了減少子類的數目,避免出現復雜的繼承關系。
概述橋接模式(Brideg Pattern)屬于結構型模式的一種,用于把抽象化與實現化解耦,使得二者可以獨立變化,它通過提供抽象化和實現化之間的橋接結構,來實現二者的解耦。
橋接模式是一種很實用的結構型設計模式,如果軟件系統中某個類存在兩個獨立變化的維度,通過該模式可以將這兩個維度分離出來,使兩者可以獨立擴展,讓系統更加符合“單一職責原則”。與多層繼承方案不同,它將兩個獨立變化的維度設計為兩個獨立的繼承等級結構,并且在抽象層建立一個抽象關聯,該關聯關系類似一條連接兩個獨立繼承結構的橋,故名橋接模式。
橋接模式用一種巧妙的方式處理多層繼承存在的問題,用抽象關聯取代了傳統的多層繼承,將類之間的靜態繼承關系轉換為動態的對象組合關系,使得系統更加靈活,并易于擴展。
合成復用原則合成復用原則又稱為組合/聚合復用原則(Composition/Aggregate Reuse Principle, CARP),指盡量使用對象組合,而不是繼承來達到復用的目的。
為什么要盡量使用合成和聚合,而不用繼承?
繼承復用破壞包裝,它把父類的實現細節直接暴露給了子類,父類發生改變,則子類也要發生相應的改變,這就直接導致了類之間的緊耦合,不利于類的擴展、復用、維護。
合成與聚合的時候,對象交互往往是通過接口或抽象類進行的,可以很好的避免上面的不足,每個新的類專注于實現自己的任務,符合單一職責原則。
案例拿汽車在路上行駛的來說。即有小汽車又有公共汽車,它們都不但能在市區中的公路上行駛,也能在高速公路上行駛。這你會發現,對于交通工具(汽車)有不同的類型,然而它們所行駛的環境(路)也在變化,在軟件系統中就要適應兩個方面的變化?怎樣實現才能應對這種變化呢?
緊耦合示例1.定義基類Road,它擁有在上面行駛的方法
class Road { public void run() { System.out.println("在路上"); } }
2.路也有多種,這時候咋整?繼承一波唄
class SpeedWay extends Road { @Override public void run() { System.out.println("在高速公路上"); } } class Street extends Road { @Override public void run() { System.out.println("在市區街道上"); } }
3.問題來了,不同的路跑著不同的車,這個時候針對不同的車都要繼承SpeedWay/Street兩個類,假設我有(5種車 5條不同的路),媽呀那得多少類,先 (2 2)吧
class CarOnSpeedWay extends SpeedWay { @Override public void run() { System.out.println("汽車在高速公路上行駛"); } } class BusOnSpeedWay extends SpeedWay { @Override public void run() { System.out.println("公共汽車在高速公路上行駛"); } } class CarOnStreet extends Street { @Override public void run() { System.out.println("汽車在街道上行駛"); } } class BusOnStreet extends Street { @Override public void run() { System.out.println("公共汽車在街道上行駛"); } }
4.編寫測試類,有木有發現代碼啰嗦不說,不是說好的一般是面向接口編程么?
public class Client { public static void main(String[] args) { CarOnSpeedWay carOnSpeedWay = new CarOnSpeedWay(); carOnSpeedWay.run(); BusOnSpeedWay busOnSpeedWay = new BusOnSpeedWay(); busOnSpeedWay.run(); CarOnStreet carOnStreet = new CarOnStreet(); carOnStreet.run(); BusOnStreet busOnStreet = new BusOnStreet(); busOnStreet.run(); } }
弊端: 仔細分析就可以發現,該方案雖然實現了功能但埋了不少坑,它在遵循開放-封閉原則的同時,違背了類的單一職責原則,即一個類只有一個引起它變化的原因,而這里引起變化的原因卻有兩個,即路類型的變化和汽車類型的變化;其次是重復代碼會很多,不同的汽車在不同的路上行駛也會有一部分的代碼是相同的;再次是類的結構過于復雜,繼承關系太多,難于維護,最后最致命的一點是擴展性太差。如果變化沿著汽車的類型和不同的道路兩個方向變化,我們會看到這個類的結構會迅速的變龐大。
松耦合示例首先我們要搞清楚,路上面可以行駛車輛(包含),但車并不是路的一部分,它們二者并不一定要緊密貼在一塊,它們之間是聚合關系
1.定義一個Car的接口,供給不同種類的去實現
interface Car { void run(); }
2.抽象類AbstractRoad,與Car關聯起來,接收具體的實現
abstract class AbstractRoad { protected Car car; public void setCar(Car car) { this.car = car; } public abstract void run(); }
3.道路類統一繼承AbstractRoad實現抽象方法,汽車類統一實現Car接口
class SpeedWay extends AbstractRoad { @Override public void run() { car.run(); System.out.println("在高速公路上"); } } class Street extends AbstractRoad { @Override public void run() { car.run(); System.out.println("在市區街道上"); } } class SmallCar implements Car { @Override public void run() { System.out.println("小汽車"); } } class BigTruck implements Car { @Override public void run() { System.out.println("大卡車"); } }
4.編寫測試類,通過傳入具體實現來獲得最終結果
public class Client { public static void main(String[] args) { Car bigTruck = new BigTruck(); Car smallCar = new SmallCar(); AbstractRoad way = new SpeedWay(); way.setCar(bigTruck); way.run(); way.setCar(smallCar); way.run(); AbstractRoad street = new Street(); street.setCar(bigTruck); street.run(); street.setCar(smallCar); street.run(); } }
可以看到,通過對象組合的方式,Bridge 模式把兩個角色之間的繼承關系改為了聚合的關系,從而使這兩者可以從容自若的各自獨立的變化,這也是Bridge模式的本意。
多維度變化結合上面的例子,增加一個維度"人",不同的人開著不同的汽車在不同的路上行駛(三個維度);結合上面增加一個類"人",并重新調用.
abstract class People { protected AbstractRoad abstractRoad; public void setAbstractRoad(AbstractRoad abstractRoad) { this.abstractRoad = abstractRoad; } public abstract void run(); } class Man extends People { @Override public void run() { System.out.println("男人開著"); abstractRoad.run(); } } public class Client { public static void main(String[] args) { Car bigTruck = new BigTruck(); People people = new Man(); AbstractRoad street = new Street(); street.setCar(bigTruck); people.setAbstractRoad(street); people.run(); } }JDBC與橋接
通常使用JDBC連接數據庫時,首選就是加載數據庫驅動,然后獲取數據庫連接
Class.forName("數據庫類驅動器"); Connection conn = DriverManager.getConnection("數據庫url", "用戶名", "密碼"); //.................
針對不同的數據庫,JDBC都可以通過java.sql.DriverManager類的靜態方法getConnection(數據庫url, 用戶名, 密碼)來獲取數據庫的連接。JDBC通過DriverManager對外提供了操作數據庫的統一接口getConnection,通過該方法可以獲取不同數據庫的連接,并且通過Connection類提供的接口來進行數據的查詢操作。
JDBC為不同的數據庫操作提供了相同的接口,但是JDBC本身并沒有針對每種數據庫提供一套具體實現代碼,而是通過接口java.sql.Driver的connect方法連接到了不同的數據庫實現。
public interface Driver { Connection connect(String url, java.util.Properties info) throws SQLException; //其他方法 }
在JDBC中,針對不同數據庫提供的統一的操作接口通過java.sql.Driver(橋)連接到了不同的數據庫實現。如連接mysql數據庫。
package com.mysql.jdbc; public class NonRegisteringDriver implements java.sql.Driver { public java.sql.Connection connect(String url, Properties info) throws SQLException { //省略具體實現代碼... } //其他方法 }具體實現
1.在com.mysql.jdbc.Driver的源碼中可以看到,類加載時會將Driver注冊到DriverManager中。
package com.mysql.jdbc; public class Driver extends NonRegisteringDriver implements java.sql.Driver { static { try { java.sql.DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException("Can"t register driver!"); } } public Driver() throws SQLException { // Required for Class.forName().newInstance() } }
2.在DriverManager中,調用getConnection會迭代驅動注冊表中的驅動,然后調用Driver接口提供的connect方法來獲取Connection對象
public class DriverManager { // JDBC驅動程序注冊表 private final static CopyOnWriteArrayListregisteredDrivers = new CopyOnWriteArrayList<>(); public static synchronized void registerDriver(java.sql.Driver driver, DriverAction da) throws SQLException { /* Register the driver if it has not already been added to our list */ if(driver != null) { registeredDrivers.addIfAbsent(new DriverInfo(driver, da)); } else { // This is for compatibility with the original DriverManager throw new NullPointerException(); } println("registerDriver: " + driver); } private static Connection getConnection(String url, java.util.Properties info, Class> caller) throws SQLException { ClassLoader callerCL = caller != null ? caller.getClassLoader() : null; synchronized(DriverManager.class) { // synchronize loading of the correct classloader. if (callerCL == null) { callerCL = Thread.currentThread().getContextClassLoader(); } } if(url == null) { throw new SQLException("The url cannot be null", "08001"); } println("DriverManager.getConnection("" + url + "")"); SQLException reason = null; //遍歷registeredDrivers表 for(DriverInfo aDriver : registeredDrivers) { // 如果沒有加載驅動的權限則跳過 if(isDriverAllowed(aDriver.driver, callerCL)) { try { //調用Driver接口提供的connect方法來獲取Connection對象 println(" trying " + aDriver.driver.getClass().getName()); Connection con = aDriver.driver.connect(url, info); if (con != null) { // Success! println("getConnection returning " + aDriver.driver.getClass().getName()); return (con); } } catch (SQLException ex) { if (reason == null) { reason = ex; } } } else { println(" skipping: " + aDriver.getClass().getName()); } } // if we got here nobody could connect. if (reason != null) { println("getConnection failed: " + reason); throw reason; } println("getConnection: no suitable driver found for "+ url); throw new SQLException("No suitable driver found for "+ url, "08001"); } } }
上述代碼中為橋接模式在JDBC中運用方式
總結實現要點
Bridge模式使用對象間的組合關系解耦了抽象和實現之間固有的綁定關系,使得抽象和實現可以沿著各自的維度來變化。
所謂抽象和實現沿著各自維度的變化,即子類化它們,得到各個子類之后,便可以任意它們,從而獲得不同路上的不同汽車。
Bridge模式有時候類似于多繼承方案,但是多繼承方案往往違背了類的單一職責原則(即一個類只有一個變化的原因),復用性比較差。Bridge模式是比多繼承方案更好的解決方法。
Bridge模式的應用一般在兩個非常強的變化維度,有時候,即使兩個維度存在變化,但是變化的地方影響并不大,換而言之兩個變化不會導致縱橫交錯的結果,并不一定要使用Bridge模式。
適用場景
如果一個系統需要在構件的抽象化角色和具體化角色之間增加更多的靈活性,避免在兩個層次之間建立靜態的聯系。
設計要求實現化角色的任何改變不應當影響客戶端,或者說實現化角色的改變對客戶端是完全透明的。
一個構件有多于一個的抽象化角色和實現化角色,系統需要它們之間進行動態耦合。
雖然在系統中使用繼承是沒有問題的,但是由于抽象化角色和具體化角色需要獨立變化,設計要求需要獨立管理這兩者。
相關模式
裝飾模式與橋接模式在一定程度上都是為了減少子類的數目,避免出現復雜的繼承關系。但是它們解決的方法卻各有不同,裝飾模式把子類中比基類中多出來的部分放到多帶帶的類里面,以適應新功能增加的需要,當我們把描述新功能的類封裝到基類的對象里面時,就得到了所需要的子類對象,這些描述新功能的類通過組合可以實現很多的功能組合.
- 說點什么參考文獻:http://blog.csdn.net/zlts000/article/details/26749723
參考文獻:https://www.cnblogs.com/houleixx/archive/2008/02/23/1078877.html
參考文件:http://www.cnblogs.com/-crazysnail/p/3977815.html
全文代碼:https://gitee.com/battcn/design-pattern/tree/master/Chapter6/battcn-brideg
個人QQ:1837307557
battcn開源群(適合新手):391619659
微信公眾號:battcn(歡迎調戲)
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/67998.html
摘要:本文只是尋找設計模式在中的應用。來補全這一塊工廠模式中的應用包線程池解釋和代碼線程池中有線程創建工廠。狀態模式中的應用解釋和代碼根據一個指針的狀態而改變自己的行為適配器模式中的應用解釋和代碼將一個類的接口轉換成客戶希望的另外一個接口。 前言 最近重學設計模式,而且還有很多源碼要看。所以就想一舉兩得。從源碼中尋找設計模式。順便還可以看看源碼。。。本文只是尋找設計模式在java中的應用。優...
摘要:該文章屬于編程中的那些經典套路設計模式匯總系列,并且以下內容基于語言今天來談談橋接模式,橋接模式的功能在于將兩個原本不相關的類結合在一起,然后利用兩個類中的方法和屬性,輸出一份新的結果。 該文章屬于《編程中的那些經典套路——設計模式匯總》系列,并且以下內容基于語言PHP 今天來談談橋接模式,橋接模式的功能在于將兩個原本不相關的類結合在一起,然后利用兩個類中的方法和屬性,輸出一份新的結果...
摘要:進階多線程開發關鍵技術后端掘金原創文章,轉載請務必將下面這段話置于文章開頭處保留超鏈接。關于中間件入門教程后端掘金前言中間件 Java 開發人員最常犯的 10 個錯誤 - 后端 - 掘金一 、把數組轉成ArrayList 為了將數組轉換為ArrayList,開發者經常... Java 9 中的 9 個新特性 - 后端 - 掘金Java 8 發布三年多之后,即將快到2017年7月下一個版...
閱讀 2061·2023-04-25 17:48
閱讀 3578·2021-09-22 15:37
閱讀 2932·2021-09-22 15:36
閱讀 5864·2021-09-22 15:06
閱讀 1634·2019-08-30 15:53
閱讀 1422·2019-08-30 15:52
閱讀 706·2019-08-30 13:48
閱讀 1116·2019-08-30 12:44