Maven中optional和scope元素的使用弄明白了嗎
橋接模式是一種結構型設計模式,英文是 Bridge Design Pattern。在 GoF 的《設計模式》一書中,橋接模式是這麼定義的:Decouple an abstraction from its implementation so that the two can vary independently。翻譯成中文就是:將抽象和實現解耦,讓它們可以獨立變化。單從官方概念來理解,相當晦澀,通常對抽象與實現的第一反應就是介面(或抽象類)與實現類的繼承關係,介面和實現獨立變化就更加讓人難以理解。
關於橋接模式,還有另外一種理解方式:一個類存在兩個(或多個)獨立變化的維度,我們通過組合的方式,讓這兩個(或多個)維度可以獨立進行擴充套件。再配合類圖(
橋接模式所涉及的角色有:
- 抽象(Abstraction)角色:抽象角色,採用抽象類方式,並持有一個實現角色物件的引用。
- 具體抽象(RefinedAbstraction)角色:具體抽象角色具體實現。
- 實現(Implementor)角色:給出實現角色的介面。實現化角色應當只給出抽象層操作,抽象角色持有抽象角色並只給出基於抽象角色的操作。
- 具體實現(ConcreteImplementor)角色:這個角色給出實現角色介面的具體實現。
可以看到抽象和實現是兩個角色,是呼叫者和被呼叫者的關係,通過組合關係使抽象角色持有實現角色,通過組合關係來替代繼承關係,避免繼承層次過多。
接下來通過程式碼理解一下橋接模式,當我們辦理銀行卡時,有不同的銀行(工商銀行,建設銀行)可選擇,每個銀行有不同的卡(儲蓄卡,信用卡),所以銀行和卡就相當於兩個獨立變化的維度,我們將銀行和卡進行抽象,將銀行作為抽象角色,將卡作為實現角色,通過組合的方式使銀行持有卡的介面引用,將其組合在一起。
public class BridgePattern { /** * 銀行抽象類,橋接模式的抽象部分 */ abstract static class Bank { protected Card card; public Bank(Card card) { this.card = card; } abstract Card applyCard(); } /** * 建設銀行實現類 */ static class CCBBank extends Bank { public CCBBank(Card card) { super(card); } @Override Card applyCard() { System.out.print("申請建設銀行 "); card.applyCard(); return card; } } /** * 工商銀行實現類 */ static class ICBCBank extends Bank { public ICBCBank(Card card) { super(card); } @Override Card applyCard() { System.out.print("申請工商銀行 "); card.applyCard(); return card; } } /** * 銀行賬號,橋接模式的實現部分介面 */ interface Card { /** * 申請卡 * * @return */ Card applyCard(); /** * 檢視卡型別 */ void showCardType(); } /** * 信用卡 */ static class CreditCard implements Card { @Override public CreditCard applyCard() { return new CreditCard(); } @Override public void showCardType() { System.out.println("信用卡"); } } /** * 儲蓄卡 */ static class DebitCard implements Card { @Override public DebitCard applyCard() { return new DebitCard(); } @Override public void showCardType() { System.out.println("儲蓄卡"); } } public static void main(String[] args) { Bank icbcBank = new ICBCBank(new CreditCard()); Card icbcCard = icbcBank.applyCard(); icbcCard.showCardType(); Bank ccbBank = new CCBBank(new DebitCard()); Card ccbCard = ccbBank.applyCard(); ccbCard.showCardType(); } }
輸出結果
申請工商銀行 信用卡
申請建設銀行 儲蓄卡
通過使用橋接模式,就使得銀行和卡這兩個維度可以獨立變化,互不干擾。
JDBC資料庫訪問介面API正是經典的橋接模式實現,具體的程式碼如下所示:
Class.forName("com.mysql.jdbc.Driver");// 載入及註冊JDBC驅動程式
String url = "jdbc:mysql://localhost:3306/mydb?user=root&password=****";
Connection con = DriverManager.getConnection(url);
如果我們想要把 MySQL 資料庫換成 Oracle 資料庫,只需要把第一行程式碼中的 com.mysql.jdbc.Driver 換成 oracle.jdbc.driver.OracleDriver便可。
com.mysql.jdbc.Driver類程式碼實現如下:
package com.mysql.jdbc;
import java.sql.DriverManager;
import java.sql.SQLException;
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
這裡涉及另外一個知識,Class.forName和ClassLoader載入類的區別:ClassLoader只能將類載入到JVM方法區(JDK8為元資料區),但是Class.forName除了載入類外,還可以直接載入類中的靜態程式碼塊。
所以當執行 Class.forName(“com.mysql.jdbc.Driver”) 這條語句的時候,會將 com.mysql.jdbc.Driver 註冊到 DriverManager 類中。
java.sql.DriverManager類程式碼精簡後實現如下:
package java.sql;
public class DriverManager {
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
private DriverManager() {
}
public static synchronized void registerDriver(java.sql.Driver driver)
throws SQLException {
registerDriver(driver, null);
}
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);
}
public static Connection getConnection(String url, String user, String password) throws SQLException {
java.util.Properties info = new java.util.Properties();
if (user != null) {
info.put("user", user);
}
if (password != null) {
info.put("password", password);
}
return (getConnection(url, info, Reflection.getCallerClass()));
}
private static Connection getConnection(String url, java.util.Properties info, Class<?> caller) throws SQLException {
for (DriverInfo aDriver : registeredDrivers) {
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);
}
}
}
}
可以看到註冊的過程無非是把Driver物件存放進入了CopyOnWriteArrayList型別的registeredDrivers變數中,獲取連線的時候,從registeredDrivers中取出對應的Driver並呼叫相應的connect方法進行連線。
JDBC 這個例子中,JDBC DriverManager就相當於“抽象”角色,java.sql.Driver相當於實現角色,具體的 Driver(比如com.mysql.jdbc.Driver)就相當於實現角色的具體實現。JDBC DriverManager 和 Driver 獨立開發,通過物件之間的組合關係,組裝在一起。JDBC 的所有邏輯操作,最終都委託給 Driver 來執行。
JDBC的類族是由SUN公司設計了一套介面,再由各個資料庫公司實現介面,我們在呼叫的過程中只需要使用介面去定義,然後在載入Driver的過程中底層程式碼會給我們選擇好介面真正的實現類,以此來實現真正的資料庫連線。
總體上來講,橋接模式的原理比較難理解,但程式碼實現相對簡單。在 GoF 的《設計模式》一書中,橋接模式被定義為:將抽象和實現解耦,讓它們可以獨立變化。還有另外一種更加簡單的理解方式:一個類存在兩個(或多個)獨立變化的維度,我們通過組合的方式,讓這兩個(或多個)維度可以獨立進行擴充套件。對於第一種 GoF 的理解方式,弄懂定義中“抽象”和“實現”兩個概念,是理解它的關鍵。定義中的“抽象”,指的並非“抽象類”或“介面”,而是被抽象出來的角色,它只包含骨架程式碼,真正的業務邏輯需要委派給定義中的“實現”來完成。而定義中的“實現”,也並非“介面的實現類”,而是一套獨立的角色。“抽象”和“實現”獨立開發,通過物件之間的組合關係,組裝在一起。
橋接模式的優點使抽象和實現可以沿著各自的維度來變化,所謂抽象和實現沿著各自維度的變化,也就是說抽象和實現不再在同一個繼承層次結構中,使它們各自都具有自己的子類,從而獲得多維度組合物件。橋接模式可以取代多層繼承方案,多層繼承方案違背了“單一職責原則”,提高了系統的可擴充套件性,在兩個變化維度中任意擴充套件一個維度,都不需要修改原有系統,符合“開閉原則”。
橋接模式的缺點是原理比較難理解,會增加系統的理解與設計難度,由於關聯關係建立在抽象層,要求開發者一開始就針對抽象層進行設計與程式設計。還要求正確識別出系統中兩個獨立變化的維度,因此其使用範圍具有一定的侷限性,如何正確識別兩個獨立維度也並非一件易事。