橋接模式
定義
抽象部分和具體實現部分分離
- 讓他們可以獨立的變化
- 通過組合的方式建立兩個類之間的關系而不是繼承
結構型模式
生活中的場景
橋
連接了兩個維度的東西
網絡連接
橋接模式
虛擬網卡和物理網卡連在一起
通用的寫法
里面有幾個關鍵角色
Abstraction——抽象化角色
它的主要職責是定義出該角色的行為,同時保存一個對實現化角色的引用,該角色一般是抽象類。
Implementor——實現化角色
它是接口或者抽象類,定義角色必需的行為和屬性。
RefinedAbstraction——修正抽象化角色
它引用實現化角色對抽象化角色進行修正。
ConcreteImplementor——具體實現化角色
它實現接口或抽象類定義的方法和屬性。
public class RefinedAbstraction extends Abstraction {
public RefinedAbstraction(IImplementor implementor) {
super(implementor);
}
@Override
public void operation() {
super.operation();
System.out.println("refined operation");
}
}
public class Test {
public static void main(String[] args) {
//定義一個實現化角色
Implementor imp = new ConcreteImplementor1();
//定義一個抽象化角色
Abstraction abs = new RefinedAbstraction(imp);
//執行行文
abs.request();
}
}
}
測試用例
public class Test {
public static void main(String[] args) {
// 來一個實現化角色
IImplementor imp = new ConcreteImplementorA();
// 來一個抽象化角色,聚合實現
Abstraction abs = new RefinedAbstraction(imp);
// 執行操作
abs.operation();
}
}
各位可能要問,為什么要增加一個構造函數?答案是為了提醒子類,你必須做這項工作,指定實現者,特別是已經明確了實現者,則盡量清晰明確地定義出來。
案例
抽象工廠的問題
以前我們在寫抽象工廠模式的時候是這樣來做的
從上圖可以看到,抽象工廠是用來幫我們創建不同的課程有相同的共性的問題
例如Java課程和python課程都需要記筆記和錄制視頻,所以我們可以通過抽象工廠CourseFactory
來創建,但是這種方式是通過繼承來做的,並且我們的產品和工廠都綁的太死了,所以我們可以通過橋梁模式來幫我們解綁,用組合和聚合來代理繼承。
橋接模式來松綁
可以看到通過AbstractCourse
給筆記和視頻搭建了一個橋梁
public class AbstractCourse implements ICourse {
private INote note;
private IVideo video;
public void setNote(INote note) {
this.note = note;
}
public void setVideo(IVideo video) {
this.video = video;
}
@Override
public String toString() {
return "AbstractCourse{" +
"note=" + note +
", video=" + video +
'}';
}
}
這樣我們就通過橋梁AbstractCourse
來幫我們建立起了筆記和視頻的聯系
消息
我們可以通過橋接模式來構建消息類型和發送消息的關系
public abstract class AbastractMessage {
private IMessage message;
public AbastractMessage(IMessage message) {
this.message = message;
}
void sendMessage(String message, String toUser) {
this.message.send(message, toUser);
}
}
使用如下
public class Test {
public static void main(String[] args) {
IMessage message = new SmsMessage();
AbastractMessage abastractMessage = new NomalMessage(message);
abastractMessage.sendMessage("加班申請","王總");
message = new EmailMessage();
abastractMessage = new UrgencyMessage(message);
abastractMessage.sendMessage("加班申請","王總");
}
}
感覺橋接模式和裝飾者有點像
源碼中的橋接模式
JDBC
初始化
當我們執行如下代碼的時候
/**
* JDBC規定了這些步驟,但是沒有具體實現。<br>
* 這些具體實現跟抽象步驟怎么銜接起來呢?通過DriverManager來橋接<br/>
*/
try {
//1.加載驅動 ---> 建立橋
Class.forName("com.mysql.jdbc.Driver"); //反射機制加載驅動類,執行靜態塊代碼,調用Driver的構造方法<br>
// 2.獲取連接Connection
//主機:端口號/數據庫名 --> 前面建立橋了 這里就用
Connection conn= DriverManager.getConnection("jdbc:mysql://localhost:3306/ds0", "admin", "admin");
// 3.得到執行sql語句的對象Statement
Statement stmt = conn.createStatement();
// 4.執行sql語句,並返回結果
ResultSet rs = stmt.executeQuery("select *from table");
}catch (Exception e){
e.printStackTrace();
}
}
會執行對應的靜態代碼塊
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!");
}
}
}
java.sql.DriverManager#registerDriver(java.sql.Driver, java.sql.DriverAction)
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = 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);
}
可以看到幫我們把對應的驅動對象包裝了一層變成了DriverInfo,並且存放起來了
獲取連接
java.sql.DriverManager#getConnection(java.lang.String, java.util.Properties, java.lang.Class<?>)
for(DriverInfo aDriver : registeredDrivers) {
// If the caller does not have permission to load the driver then
// skip it.
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
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());
}
}
可以看到獲取連接的時候,從之前已經存起來的驅動里面來獲取連接,並且返回出去
通過一個集合幫我們構建了一個橋梁
總結
適用場景
- 抽象和實現分離
- 能夠幫我們解決繼承的問題
- 單繼承
- 暴露過多屬性給子類
- 或者因為多繼承導致類的個數劇增
- 無法喜更細化場景
- 能夠幫我們解決繼承的問題
- 優秀的擴展能力
- 我們橋梁都是面向抽象的
- 增加橋也是能夠快速增加的
- 一個類存在兩個或者多個獨立變化的維度
- 這兩個維度都需要獨立進行擴展
- 接口或者抽象類不穩定的場景
- 如果通過繼承,修改的會非常多
- 重用性要求高的場景
- 繼承會導致受到父類的限制,粒度不會太細
優點
- 分離了抽象部分和具體實現部分
- 我們通過橋梁可以解決繼承的缺點
- 提高了系統的擴展
- 符合開閉原則
- 符合合成復用原則
缺點
- 增加了系統的理解和設計難度
- 需要正確的識別系統中兩個獨立變化的維度
和其他設計模式的關聯
橋接模式更加注重形式
橋接模式更加注重連接,適配器模式更加注重適配
注意
不能說繼承不好,它非常好,但是有缺點,我們可以揚長避短,對 於比較明確不發生變化的,則通過繼承來完成;若不能確定是否會發生 變化的,那就認為是會發生變化,則通過橋梁模式來解決,這才是一個完美的世界。
問題
想一下橋接模式怎么不使用繼承的情況下怎么把兩個維度結合起來的呢,可以舉個例子或者對於上述案例加以說明也行
-
在java當中除了繼承就是組合和聚合了
-
如果通過繼承
- 那么子類會持有很多父類的東西,並且很容易重寫父類的方法,違背里氏替換原則
-
如果通過組合和聚合
- 子類想要擁有方法很簡單,橋梁打過去,獲得這個方法就行了
- 例如
我們集團組要功能是賺錢,賺錢需要先生產然后銷售
crop
當前是抽象類具體的生產產品,和銷售交給旗下的子公司HouseCrop
賣房子的和IpodCorp
賣Ipod的來賣錢 -
public class Client { public static void main(String[] args) { System.out.println("-------房地產公司是這樣運行的------"); //先找到我的公司 HouseCorp houseCorp = new HouseCorp(); //看我怎么掙錢 houseCorp.makeMoney(); System.out.println("\n"); System.out.println("-------服裝公司是這樣運行的-------"); ClothesCorp clothesCorp = new ClothesCorp(); clothesCorp.makeMoney(); } }
很明顯現在有一個問題,我們的工廠和具體的產品綁的太死了,比如說但是我們不想用抽象工廠那么可以通過橋接模式來解決,用組合來把他們構建起來
可以讓山寨的工廠也能生產房子和ipod其實就是通過橋梁模式讓我們的具體公司能夠和產品關聯起來
-
我們平常寫JDBC代碼的時候有沒有去琢磨一下這句代碼
DriverManager.getConnection("jdbc:mysql://localhost:3306/ds0", "admin", "admin");
是怎么拿到mysqlConnection。
我的筆記倉庫地址gitee 快來給我點個Star吧