Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> java/android 設計模式學習筆記(8)---橋接模式

java/android 設計模式學習筆記(8)---橋接模式

編輯:關於Android編程

這篇博客我們來介紹一下橋接模式(Bridge Pattern),它也是結構型設計模式之一。橋接,顧名思義,就是用來連接兩個部分,使得兩個部分可以互相通訊或者使用,橋接模式的作用就是為被分離了的抽象部分和實現部分搭橋。在現實生活中也有很多這樣的例子,一個物品在搭配不同的配件時會產生不同的動作和結果,例如一輛賽車搭配的是硬胎或者是軟胎就能夠在干燥的馬路上行駛,而如果要在下雨的路面行駛,就需要搭配雨胎了,這種根據行駛的路面不同,需要搭配不同的輪胎的變化的情況,我們從軟件設計的角度來分析,就是一個系統由於自身的邏輯,會有兩個或多個維度的變化,有時還會形成一種樹狀的關系,而為了應對這種變化,我們就可以使用橋接模式來進行系統的解耦。
  橋接模式,作用是將一個系統的抽象部分和實現部分分離,使它們都可以獨立地進行變化,對應到上面就是賽車的種類可以相對變化,輪胎的種類可以相對變化,形成一種交叉的關系,最後的結果就是一種賽車對應一種輪胎就能夠成功產生一種結果和行為。

特點

將抽象部分與實現部分分離,使他們都可以獨立地進行變化。為了達到讓抽象部分和實現部分獨立變化的目的,抽象部分會擁有實現部分的接口對象,有了實現部分的接口對象之後,就能夠通過這個接口來調用具體實現部分的功能。橋接在程序上就體現成了抽象部分擁有實現部分的接口對象,維護橋接就是維護這個關系,也就是說,橋接模式中的橋接是一個單方向的關系,只能夠抽象部分去使用實現部分的對象,而不能反過來。
  橋接模式適用於以下的情形:

如果一個系統需要在構建的抽象化角色和具體化角色之間增加更多的靈活性,避免在兩個層次之間建立靜態的繼承聯系,可以通過橋接模式使他們在抽象層建立一個關聯關系;那些不希望使用繼承或因為多層次繼承導致系統類的個數極具增加的系統;一個類存在兩個獨立變化的維度,而這兩個維度都需要進行擴展。

 

UML類圖

我們來看看橋接模式的 uml 圖:
這裡寫圖片描述
橋接模式也分為四個角色:<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCkFic3RyYWN0aW9uo7qz6c/zsr+31rjDwOCxo7PW0ru49rbUyrXP1rK/t9a21M/ztcTS/dPDo6yz6c/zsr+31tbQtcS3vbeo0OjSqrX308PKtc/Wsr+31rXEttTP88C0yrXP1qOsuMPA4NK7sOPOqrPpz/PA4KO7UmVmaW5lZEFic3RyYWN0aW9uo7rTxbuvtcSz6c/zsr+31rPpz/Oyv7fWtcS+38zlyrXP1qOsuMPA4NK7sOO21LPpz/Oyv7fWtcS3vbeovfjQ0M3qyca6zcCp1bmju0ltcGxlbWVudG9yo7rKtc/Wsr+31r/J0tTOqr3Tv9q78tXfysez6c/zwOCjrMbkt723qLK70ru2qNKq0+uz6c/zsr+31tbQtcTSu9bCo6zSu7Djx+m/9s/CysfTycq1z9ayv7fWzOG5qbv5sb61xLLZ1/ejrLb4s+nP87K/t9a2qNLltcTU8srHu/nT2sq1z9ayv7fWu/mxvrLZ1/e1xNK1zvG3vbeoo7tDb25jcmV0ZUltcGxlbWVudG9yQSC6zSBDb25jcmV0ZUltcGxlbWVudG9yQiCjusq1z9ayv7fWtcS+38zlyrXP1s3qycbKtc/Wsr+31tbQtqjS5bXEvt/M5cLfvK2ho6GhoaG7+dPatMvO0sPHvs2/ydLU0LSz9sfFvdPEo8q9tcTNqNPDtPrC66Osz8jKx8q1z9ayv7fWtcS0+sLro7o8YnIgLz4NCjxzdHJvbmc+SW1wbGVtZW50b3IuY2xhc3M8L3N0cm9uZz4NCjxwPiZuYnNwOzwvcD4NCjxwcmUgY2xhc3M9"brush:java;"> public interface Implementor { void operationImpl(); }

ConcreteImplementorA.class

public class ConcreteImplementorA implements Implementor{
    @Override
    public void operationImpl() {
        //具體實現
    }
}

ConcreteImplementorB.class

public class ConcreteImplementorB implements Implementor{
    @Override
    public void operationImpl() {
        //具體實現
    }
}

然後是抽象部分的代碼:
Abstraction.class

public abstract class Abstraction {
    private Implementor implementor;

    public Abstraction(Implementor implementor) {
        this.implementor = implementor;
    }

    public void operation() {
        implementor.operationImpl();
    }
}

RefinedAbstraction.class

public class RefinedAbstraction extends Abstraction{
    public RefinedAbstraction(Implementor implementor) {
        super(implementor);
    }

    public void refinedOperation() {
        //對 Abstraction 中的 operation 方法進行擴展
    }
}

看了這段通用代碼之後,橋接模式的結構應該就很清楚了,需要注意的一點是 RefinedAbstraction 類根據實際情況是可以有多個的。
  當然上面的 uml 類圖和通用代碼只是最常用的實現方式而已,在實際使用中可能會有其他的情況,比如 Implementor 只有一個類的情況,雖然這時候可以不去創建 Implementor 接口,精簡類的層次,但是我建議還是需要抽象出實現部分的接口,在我們下面要講到的 Android 源碼橋接模式分析中,Android 系統就算只有一個實現部分具體類,也抽象出了一個實現接口,可以學習一下。總而言之,保持分離狀態,這樣的話,他們才不會互相影響,才可以分別擴展。

Android 源碼橋接模式分析

橋接模式在 Android 源碼中的使用頻率也是很高的,這裡就分析一下最典型的 Window 與 WindowManager 之間的橋接模式,先來看看他們的 uml 圖:
這裡寫圖片描述
Window 類和 PhoneWindow 類為抽象部分,PhoneWindow 為 Window 類的唯一實現子類,在 Window 類中,持有了一個 WindowManager 類的引用,並且在 setWindowManager 方法中將其賦值為了 WindowManagerImpl 對象:

public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
        boolean hardwareAccelerated) {
    ...
    mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}

對應著的 WindowManager 接口和 WindowManagerImpl 類就組成了實現部分,所以說這四個類使用的就是典型的橋接模式,Window 中的 addView,removeView 等操作都橋接給了 WindowManagerImpl 去處理,這個我在以前的博客:android WindowManager解析與騙取QQ密碼案例分析中也已經講到過。但是實際上 WindowManagerImpl 中並沒有去實現 addView 方法,在 WindowManagerImpl 類中持有了一個 WindowManagerGlobal 對象的引用,這個引用是通過單例模式獲取的,而 addView 等方法就直接交給了 WindowManagerGlobal 類去處理:

public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    mGlobal.addView(view, params, mDisplay, mParentWindow);
}

繼續分析 WindowManagerGlobal 類的 addView 函數,在該函數中,最後調用到了 ViewRootImpl 類中的 setView 成員方法(這個調用過程這裡我就不詳細介紹了,在博客:android 不能在子線程中更新ui的討論和分析有講到,感興趣的可以去看看),ViewRootImpl 類中的 setView 方法最後會調用到 scheduleTraversals 方法,scheduleTraversals 方法:

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        ...
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

我們繼續看看 pokeDrawLockIfNeeded 函數:

void pokeDrawLockIfNeeded() {
    final int displayState = mAttachInfo.mDisplayState;
    if (mView != null && mAdded && mTraversalScheduled
            && (displayState == Display.STATE_DOZE
                    || displayState == Display.STATE_DOZE_SUSPEND)) {
        try {
            mWindowSession.pokeDrawLock(mWindow);
        } catch (RemoteException ex) {
            // System server died, oh well.
        }
    }
}

看到了 mWindowSession 對象,是不是很熟悉,mWindowSession 對象的創建:

 mWindowSession = WindowManagerGlobal.getWindowSession();

嗯,又回到了 WindowManagerGlobal 類,去看看它的 getWindowSession 方法:

public static IWindowSession getWindowSession() {
    synchronized (WindowManagerGlobal.class) {
        if (sWindowSession == null) {
            try {
                InputMethodManager imm = InputMethodManager.getInstance();
                IWindowManager windowManager = getWindowManagerService();
                sWindowSession = windowManager.openSession(
                        new IWindowSessionCallback.Stub() {
                            @Override
                            public void onAnimatorScaleChanged(float scale) {
                                ValueAnimator.setDurationScale(scale);
                            }
                        },
                        imm.getClient(), imm.getInputContext());
            } catch (RemoteException e) {
                Log.e(TAG, "Failed to open window session", e);
            }
        }
        return sWindowSession;
    }
}

WindowSession 是通過 IWindowManager 創建的:

public static IWindowManager getWindowManagerService() {
    synchronized (WindowManagerGlobal.class) {
        if (sWindowManagerService == null) {
            sWindowManagerService = IWindowManager.Stub.asInterface(
                    ServiceManager.getService("window"));
            try {
                sWindowManagerService = getWindowManagerService();
                ValueAnimator.setDurationScale(sWindowManagerService.getCurrentAnimatorScale());
            } catch (RemoteException e) {
                Log.e(TAG, "Failed to get WindowManagerService, cannot set animator scale", e);
            }
        }
        return sWindowManagerService;
    }
}

看了這段代碼是不是就一下子清楚了,跨進程之間的通信,最終調用到的是 WindowManagerService 中。這裡需要簡單提到的一句是 WMS 和 AMS 一樣都是由 SystemServer 啟動的,是在另一個進程中的,這也是為什麼需要跨進程之間的通信。

示例與源碼

以上面說到的車與輪胎的關系來作為一個例子,車為抽象部分,輪胎為實現部分,抽象部分的相關類:
Car.class

public abstract class Car {
    private ITire tire;

    public Car(ITire tire) {
        this.tire = tire;
    }

    public ITire getTire() {
        return tire;
    }

    public abstract void run();
}

RacingCar.class

public class RacingCar extends Car{
    public RacingCar(ITire tire) {
        super(tire);
    }

    @Override
    public void run() {
        Log.e("shawn", "racing car " + getTire().run());
    }
}

SedanCar.class

public class SedanCar extends Car{
    public SedanCar(ITire tire) {
        super(tire);
    }

    @Override
    public void run() {
        Log.e("shawn", "sedan car " + getTire().run());
    }
}

實現部分:
ITire.class

public interface ITire {
    String run();
}

RainyTire.class

public class RainyTire implements ITire{
    @Override
    public String run() {
        return "run on the rainy road.";
    }
}

SandyTire.class

public class SandyTire implements ITire{
    @Override
    public String run() {
        return "run on the sandy road.";
    }
}

測試代碼:

Car car = null;
switch (v.getId()) {
    case R.id.btn_sedanCar_with_rainyTire:
        car = new SedanCar(new RainyTire());
        break;
    case R.id.btn_sedanCar_with_sandyTire:
        car = new SedanCar(new SandyTire());
        break;
    case R.id.btn_racingCar_with_rainyTire:
        car = new RacingCar(new RainyTire());
        break;
    case R.id.btn_racingCar_with_sandyTire:
        car = new RacingCar(new SandyTire());
        break;
}
car.run();

最後的結果:

com.android.bridgepattern E/shawn: sedan car run on the rainy road.
com.android.bridgepattern E/shawn: sedan car run on the sandy road.
com.android.bridgepattern E/shawn: racing car run on the rainy road.
com.android.bridgepattern E/shawn: racing car run on the sandy road

例子很簡單,一目了然,使用橋接模式的優點大家也能夠看出來, Car 和 Tire 的邏輯完全分離,各自搭配,大大降低了耦合度。

總結

橋接模式的目的是為了將抽象部分與實現部分解耦,可以將一個 N * N 的系統進行拆分,減少類的數量。橋接模式適合使用在需要跨越多個平台的圖形和窗口系統上,優點很明顯:

將實現予以解耦,讓它和抽象之間不再永久綁定,使用組合的關系,降低耦合度,符合開閉原則;抽象和實現之間可以獨立擴展,不會影響到彼此;對於“具體抽象類”所做的改變,不會影響到客戶端。但是橋接模式的缺點也很明顯,一個是增加了系統的復雜度,二個是不容易設計,對於抽象和實現的分離把握,是不是需要分離,如何分離等這些問題對於設計者來說要有一個恰到好處的分寸,理解橋接模式很容易,要設計合理不容易。
  橋接有時候類似於多繼承方案,但是多繼承方案往往違背了類的單一職責原則(即一個類只有一個變化的原因),復用性比較差,橋接模式是比多繼承方案更好的解決方法。

 

源碼下載

https://github.com/zhaozepeng/Design-Patterns/tree/master/BridgePattern

 
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved