橋接模式介紹
橋接模式(Bridge Pattern)也稱為橋梁模式,是結(jié)構(gòu)型設(shè)計(jì)模式之一。顧名思義其與現(xiàn)實(shí)中的橋梁作用相同,有連接兩岸的作用。
橋接模式定義
將抽象部分和實(shí)現(xiàn)部分分離,使它們都可以獨(dú)立地進(jìn)行變化。
橋接模式使用場景
- 對于那些不希望使用繼承或因?yàn)槎鄬哟卫^承導(dǎo)致系統(tǒng)類的個數(shù)急劇增加的系統(tǒng),橋接模式尤為適用。
- 一個類存在兩個獨(dú)立變化的維度,且這兩個維度都需要進(jìn)行擴(kuò)展。
橋接模式 UML 圖

角色介紹:
- Abstraction:抽象部分。該類保持了一個對象實(shí)現(xiàn)部分的引用,該類或者繼承類可通過調(diào)用實(shí)現(xiàn)部分對象的方法來完成一些功能。
- RefinedAbstraction:優(yōu)化的抽象部分。該類是對抽象部分的方法進(jìn)行完善和擴(kuò)展。
- Implementor:實(shí)現(xiàn)部分。定義一類功能的基本操作。
- ConcreteImplementor:具體實(shí)現(xiàn)部分。
橋接模式實(shí)現(xiàn)
現(xiàn)實(shí)生活中橋接模式的應(yīng)用很多,比如喝咖啡,杯子大小可以分為大杯、小杯,甜度可以分為加糖、不加糖,杯子大小與加糖與否是獨(dú)立變化的,但對于咖啡兩者又存在聯(lián)系;再比如筆,粗細(xì)可以分為0.7mm 筆頭,0.5mm筆頭,顏色可以分為紅色、黑色,兩者彼此獨(dú)立,也存在聯(lián)系。
看完例子可能并不能體會到橋接模式,下面就筆的生產(chǎn)為例,筆頭有0.7mm、有0.5mm筆頭,暫且我們以大、小來區(qū)分,我們設(shè)計(jì)的類結(jié)構(gòu)如下:

沒毛病,這時假如我們要生產(chǎn)兩種顏色的筆,分別為黑色、紅色。這時比較直接的設(shè)計(jì)如下:

看似沒毛病,細(xì)思極恐,這才是項(xiàng)目初期,假如我們筆的粗細(xì)還增加了0.3mm,0.25mm等,顏色增加到了 5 種顏色;再假如我們除了支持筆的粗細(xì)、筆的顏色、還支持帶筆帽、或者為自動筆等,這樣顯著問題就是類的數(shù)量爆棚,另一個問題就是牽一發(fā)而動全身。對象的繼承關(guān)系實(shí)在編譯就定義好了,所以無法運(yùn)行時改變從父類繼承的實(shí)現(xiàn)。子類的實(shí)現(xiàn)與它的父類就非常緊密的依賴,以至于父類實(shí)現(xiàn)中的任何變化都會導(dǎo)致子類的變化。這種依賴關(guān)系限制了靈活性并最終限制了復(fù)用性。
筆的大小與顏色變化獨(dú)立,屬于兩個維度,我們可以將其分離,重新設(shè)計(jì)如下:

這樣筆的大小和顏色就分離開來,兩者可以獨(dú)立變化,筆支持持有了顏色的引用,在這里是聚合的關(guān)系,當(dāng)需要生產(chǎn)黑色 0.77mm筆時,只需要在 BigPen 中通過Color的應(yīng)用設(shè)置顏色即可。這種就是橋接模式。
抽象部分(Abstraction)
public abstract class Pen {
protected Color color;
public Pen(Color color) {
this.color = color;
}
public abstract void draw();
}
優(yōu)化的抽象部分(RefinedAbstraction)
public class BigPen extends Pen {
public BigPen(Color color) {
super(color);
}
@Override
public void draw() {
System.out.println("0.7mm的" + color.makeColor() + "筆");
}
}
public class SmallPen extends Pen {
public SmallPen(Color color) {
super(color);
}
@Override
public void draw() {
System.out.println("0.5mm的" + color.makeColor() + "筆");
}
}
實(shí)現(xiàn)部分
public interface Color {
String makeColor();
}
具體的實(shí)現(xiàn)部分
public class BlackColor implements Color {
@Override
public String makeColor() {
return "黑色";
}
}
public class RedColor implements Color {
@Override
public String makeColor() {
return "紅色";
}
}
客戶端
public class Client {
public static void main(String[] args) {
Color redColor = new RedColor();
Color blackColor = new BlackColor();
Pen pen1 = new BigPen(redColor);
Pen pen2 = new BigPen(blackColor);
Pen pen3 = new SmallPen(redColor);
Pen pen4 = new SmallPen(blackColor);
pen1.draw();
pen2.draw();
pen3.draw();
pen4.draw();
}
}
運(yùn)行結(jié)果:
0.7mm的紅色筆
0.7mm的黑色筆
0.5mm的紅色筆
0.5mm的黑色筆
如果需要增加是否帶筆帽,只需新建接口,在 Pen 中傳入即可,只增加必要的類,并且每個維度可以獨(dú)立變化,也符合了開放-封閉原則。
總結(jié)
優(yōu)先使用對象的組合、聚合有助于保持每個類的封裝,并被集中在單個任務(wù)上。這樣類和類繼承層次可以保持較小規(guī)模。
優(yōu)點(diǎn)
1.分離抽象接口及其實(shí)現(xiàn)部分。提高了比繼承更好的解決方案。
2.橋接模式提高了系統(tǒng)的可擴(kuò)充性,在兩個變化維度中任意擴(kuò)展一個維度,都不需要修改原有系統(tǒng)。
3.實(shí)現(xiàn)細(xì)節(jié)對客戶透明,客戶無需關(guān)心實(shí)現(xiàn)細(xì)節(jié),它已經(jīng)由抽象層通過聚合關(guān)系完成了封裝。
注意事項(xiàng)
橋接模式是非常簡單的,使用該模式時主要考慮如何拆分抽象和實(shí)現(xiàn),并不是一涉及繼承就要考慮使用該模式,那還要繼承干什么呢?橋接模式的意圖還是對變化的封裝,盡量把可能變化的因素封裝到最細(xì)、最小的邏輯單元中,避免風(fēng)險(xiǎn)擴(kuò)散。因此讀者在進(jìn)行系統(tǒng)設(shè)計(jì)時,發(fā)現(xiàn)類的繼承有N層時,可以考慮使用橋接模式。
Android 源碼中的橋接模式
在應(yīng)用層,對于普通控件、View 和 Canvas 就可以看做橋接模式,View 為抽象角色,Canvas 為實(shí)現(xiàn)角色。
在 Framwork 內(nèi)部的源碼中,比較經(jīng)典的就是 Window 、WindowManager 和 WindowManagerService 之間的關(guān)系,如下圖:

- Window 和 PhoneWindow 構(gòu)成了窗口的抽象部分,Window 為抽象接口,PhoneWindow 為抽象部分的具體實(shí)現(xiàn)及擴(kuò)展。
- WindowManager 為實(shí)現(xiàn)部分的實(shí)現(xiàn)接口,WindowManagerImpl 為實(shí)現(xiàn)部分的具體邏輯實(shí)現(xiàn)。
這就是橋接模式。其中 WindowManagerImpl 使用 WindowManagerGlobal 對象,通過 IWindowManager 接口與 WindowManagerService 進(jìn)行交互,并有 WMS 完成具體的窗口管理工作。
Window 和 WindowManager 的橋梁搭建的主要代碼如下:
Window.java
public abstract class Window {
private WindowManager mWindowManager;
...
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
mAppToken = appToken;
mAppName = appName;
mHardwareAccelerated = hardwareAccelerated
|| SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
public WindowManager getWindowManager() {
return mWindowManager;
}
}