Java/Android 设计模式系列(8)--桥接模式

  这篇博客我们来介绍一下桥接模式(Bridge Pattern),它也是结构型设计模式之一。桥接,顾名思义,就是用来连接两个部分,使得两个部分可以互相通讯或者使用,桥接模式的作用就是为被分离了的抽象部分和实现部分搭桥。在现实生活中也有很多这样的例子,一个物品在搭配不同的配件时会产生不同的动作和结果,例如一辆赛车搭配的是硬胎或者是软胎就能够在干燥的马路上行驶,而如果要在下雨的路面行驶,就需要搭配雨胎了,这种根据行驶的路面不同,需要搭配不同的轮胎的变化的情况,我们从软件设计的角度来分析,就是一个系统由于自身的逻辑,会有两个或多个维度的变化,有时还会形成一种树状的关系,而为了应对这种变化,我们就可以使用桥接模式来进行系统的解耦。
  桥接模式,作用是将一个系统的抽象部分和实现部分分离,使它们都可以独立地进行变化,对应到上面就是赛车的种类可以相对变化,轮胎的种类可以相对变化,形成一种交叉的关系,最后的结果就是一种赛车对应一种轮胎就能够成功产生一种结果和行为。

设计模式总目录

  Java/Android 设计模式系列–目录

特点

  将抽象部分与实现部分分离,使他们都可以独立地进行变化。为了达到让抽象部分和实现部分独立变化的目的,抽象部分会拥有实现部分的接口对象,有了实现部分的接口对象之后,就能够通过这个接口来调用具体实现部分的功能。桥接在程序上就体现成了抽象部分拥有实现部分的接口对象,维护桥接就是维护这个关系,也就是说,桥接模式中的桥接是一个单方向的关系,只能够抽象部分去使用实现部分的对象,而不能反过来。
  桥接模式适用于以下的情形:

  • 如果一个系统需要在构建的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,可以通过桥接模式使他们在抽象层建立一个关联关系;
  • 那些不希望使用继承或因为多层次继承导致系统类的个数极具增加的系统;
  • 一个类存在两个独立变化的维度,而这两个维度都需要进行扩展。

UML类图

  我们来看看桥接模式的 uml 图:
  这里写图片描述
桥接模式也分为四个角色:

  • Abstraction:抽象部分

  • RefinedAbstraction:优化的抽象部分

  • Implementor:实现部分

  • ConcreteImplementorA 和 ConcreteImplementorB :实现部分的具体实现

      基于此我们就可以写出桥接模式的通用代码,先是实现部分的代码:
    Implementor.class

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 不能在子线程中更新ui的讨论和分析:Activity 打开的过程分析;
  java/android 设计模式学习笔记(9)—代理模式:AMS 的相关类图和介绍;
  android WindowManager解析与骗取QQ密码案例分析:界面 window 的创建过程;
  java/android 设计模式学习笔记(8)—桥接模式:WMS 的相关类图和介绍;
  android IPC通信(下)-AIDL:AIDL 以及 Binder 的相关介绍;
  Android 动态代理以及利用动态代理实现 ServiceHook:ServiceHook 的相关介绍;
  Android TransactionTooLargeException 解析,思考与监控方案:TransactionTooLargeException 的解析以及监控方案。  
  桥接模式在 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;
    }
}

看了这段代码是不是就一下子清楚了(不清楚的可以看看我的博客:android IPC通信(下)-AIDL),跨进程之间的通信,最终调用到的是 WindowManagerService 中,关于 WindowManagerService 后续的介绍大家可以去看看老罗的博客:Android窗口管理服务WindowManagerService的简要介绍和学习计划,确实很详细。这里需要简单提到的一句是 WMS 和 AMS 一样都是由 SystemServer 启动的,是在另一个进程中的,这也是为什么需要跨进程之间的通信,以前我写过一篇博客:android启动过程详细讲解,里面有介绍到 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/Jakey-jp/Design-Patterns/tree/master/BridgePattern

引用

https://en.wikipedia.org/wiki/Bridge_pattern
http://blog.csdn.net/jason0539/article/details/22568865

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值