0、背景
加入一個手機分為多種款式,不同款式分為不同品牌。這些詳細分類下分別進行操作。
如果傳統做法,需要將手機,分為不同的子類,再繼續分,基本屬於一個龐大的多叉樹,然後每個葉子節點進行相同名稱、但是細節不同的功能實現。
問題:
- 類爆炸:類的增加基本沒有任何優化,多一個就要妥妥的增加類;
- 違反單一原則:增加一個品牌,影響每種型別下的這個品牌,增加一個型別,影響每個品牌的這個型別。
一、橋接模式
解決上面說的問題的方式就是使用橋接模式。
橋接(Bridge)模式是指,將實現和抽象放在兩個不同的類層次中,使得兩個層次可以獨立改變。
橋接模式是一種結構型設計模式,基於類的最小設計原則,讓不同的類承擔不同的職責。(單一原則)
橋接模式的主要特點是,把抽象 Abstraction 和行為實現 Implementation 分離開,從而保持各部分的獨立和應對他們的功能擴充套件。
類圖如下所示:
其中,承擔 橋接功能 的抽象類就是 Abstraction。
看起來還是很抽象,結合上面的手機問題,畫出對應的類圖,再結合來看:
也就是說,比起原來的樹狀結構,用橋接模式,將行為實現放在一邊,有自己的介面;將抽象出的另一個維度放在一邊,有自己的抽象父類和子類,然後將上層的抽象類和實現介面相關聯。
這樣的話,前面遇到的問題:
- 新增手機樣式,就在左邊新增抽象類定義就可以;
- 新增品牌,在右邊新增實現類就可以。
- 其他部分都不會被影響。
觀察上面的圖,橋接的意味也很明顯,當 QuanPingPhone 使用一個 call 方法的時候,繼承的其實是父類的方法,而這裡面組合了另一個性質,比如是 Oppo 手機的 call 方法,那麼就是通過橋接一路引過去的。 (忽略我的英語)
按照這個思路我們來寫一個實現:
/*
品牌,介面
*/
public interface Brand {
void open();
void close();
void call();
}
public class Xiaomi implements Brand{
@Override
public void open() {
System.out.println("小米手機開機");
}
@Override
public void close() {
System.out.println("小米手機關機");
}
@Override
public void call() {
System.out.println("小米手機打電話");
}
}
public class Vivo implements Brand{
@Override
public void open() {
System.out.println("vivo手機開機");
}
@Override
public void close() {
System.out.println("vivo手機關機");
}
@Override
public void call() {
System.out.println("vivo手機打電話");
}
}
/*
抽象層:手機,通過品牌介面聚合不同品牌
*/
public abstract class Phone {
//品牌
private Brand brand;
public Phone(Brand brand) {
this.brand = brand;
}
protected void open(){
this.brand.open();
}
protected void close(){
this.brand.close();
}
protected void call(){
this.brand.call();
}
}
/*
摺疊手機,繼承抽象類手機
*/
public class BoldPhone extends Phone{
//構造器
public BoldPhone(Brand brand) {
super(brand);
}
public void open(){
super.open();//實際上通過父類的open,橋接到了brand下面的某個子類的open
System.out.println("開啟的是摺疊手機");
}
public void close(){
super.close();
System.out.println("關閉的是摺疊手機");
}
public void call(){
super.call();
System.out.println("打電話的的是摺疊手機");
}
}
/*
全面屏手機,繼承抽象類手機
*/
public class ScreenPhone extends Phone{
public ScreenPhone(Brand brand) {
super(brand);
}
public void open(){
super.open();//實際上通過父類的open,橋接到了brand下面的某個子類的open
System.out.println("開啟的是全面屏手機");
}
public void close(){
super.close();
System.out.println("關閉的是全面屏手機");
}
public void call(){
super.call();
System.out.println("打電話的的是全面屏手機");
}
}
呼叫試試:
/*
客戶端
*/
public class Client {
public static void main(String[] args) {
//獲取一個手機,需要的是樣式+手機兩個實現類
//通過Phone這個抽象類,增加一個樣式為BoldPhone的手機,橋接到品牌為Xiaomi
Phone phone = new BoldPhone(new Xiaomi());
phone.open();
phone.call();
phone.close();
}
}
這樣的話,如果擴充套件起來,比如新增加一種樣式的手機,我們只需要繼承Phone再寫一個子類,其餘都不需要更改,如果新增加一個品牌的手機,只需要實現Band介面再寫一個實現類,其餘都不需要更改。
二、使用橋接模式的原始碼:JDBC
JDBC 原始碼裡:
MySQL 的 Connection 介面實現的是 java.sql.Connection 介面,同時 Oracle 資料庫也一樣可以實現 java.sql.Connection 介面,他們向下都可以有更多的實現子類。
然後 DriverManager 相當於橋接模組,依賴和聚合 java.sql.Connection 介面,供客戶端呼叫。
和我們上面所說的略微不同,就是 DriverManager 不是抽象類,而是直接的具體實現。
三、橋接模式的注意事項
- 橋接模式實現了抽象和實現部分的分離,提高了系統的靈活性,讓抽象部分和實現部分獨立開來,有助於系統的分層設計。
- 高層只需要知道抽象部分和實現部分的介面,而不需要知道其他具體實現;
- 替代了多層繼承,大大減少了子類的數量;
- 橋接模式的引入,增加了系統的設計和理解難度,由於聚合關聯關係建立在抽象層,要求開發者針能對抽象層進行設計和程式設計;
- 適用範圍有侷限性
應用場景:
- jdbc:多種驅動,多種資料庫;
- 銀行轉賬:因為轉賬動作要分很多類,使用者也分很多類,所以他們之間不適合多層繼承;
- 訊息管理:訊息型別不同,訊息傳送方式不同。