java/android 设计模式学习笔记(8)---桥接模式

31 篇文章 1 订阅
31 篇文章 3 订阅

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

设计模式总目录

  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 的系统进行拆分,减少类的数量。桥接模式适合使用在需要跨越多个平台的图形和窗口系统上,优点很明显:

    • 将实现予以解耦,让它和抽象之间不再永久绑定,使用组合的关系,降低耦合度,符合开闭原则
    • 抽象和实现之间可以独立扩展,不会影响到彼此;
    • 对于“具体抽象类”所做的改变,不会影响到客户端。
    但是桥接模式的缺点也很明显,一个是增加了系统的复杂度,二个是不容易设计,对于抽象和实现的分离把握,是不是需要分离,如何分离等这些问题对于设计者来说要有一个恰到好处的分寸,理解桥接模式很容易,要设计合理不容易。
      桥接有时候类似于多继承方案,但是多继承方案往往违背了类的单一职责原则(即一个类只有一个变化的原因),复用性比较差,桥接模式是比多继承方案更好的解决方法,这点与 装饰者模式类似。

    适配器 VS 装饰者 VS 桥接 VS 代理 VS 外观

      这几个都是结构型设计模式,他们有些类似,在实际使用过程中也容易搞混,我们在这就给他们做一个对比:

    适配器模式

      适配器模式和其他三个设计模式一般不容易搞混,它的作用是将原来不兼容的两个类融合在一起,uml 图也和其他的差别很大。
      uml 类图:
      这里写图片描述

    装饰者模式

      装饰者模式结构上类似于代理模式,但是和代理模式的目的是不一样的,装饰者是用来动态地给一个对象添加一些额外的职责,装饰者模式为对象加上行为,而代理则是控制访问
      uml 类图:
      这里写图片描述

    桥接模式

      桥接模式的目的是为了将抽象部分与实现部分分离,使他们都可以独立地进行变化,所以说他们两个部分是独立的,没有实现自同一个接口,这是桥接模式与代理模式,装饰者模式的区别
      uml 类图:
      这里写图片描述

    代理模式

      代理模式为另一个对象提供代表,以便控制客户对对象的访问,管理的方式有很多种,比如远程代理和虚拟代理等,这个在上面有,这里就不说了,而装饰者模式则是为了扩展对象。
      uml 类图:
      这里写图片描述

    外观模式

      外观模式提供一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。
      适配器模式将一个或多个类接口变成客户端所期望的一个接口,虽然大多数资料所采用的例子中适配器只适配一个类,但是你可以适配许多类来提供一个接口让客户端访问;类似的,外观模式 也可以只针对一个拥有复杂接口的类提供简化的接口,两种模式的差异,不在于他们“包装”了几个类,而是在于它们的意图。适配器模式 的意图是,“改变”接口符合客户的期望;而外观模式的意图是,提供子系统的一个简化接口。
      uml类图:
      这里写图片描述

    源码下载

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

    引用

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

    • 7
      点赞
    • 27
      收藏
      觉得还不错? 一键收藏
    • 3
      评论
    评论 3
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

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

    抵扣说明:

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

    余额充值