“假舆马者,非利足也,而致千里;假舟楫者,非能水也,而绝江河。君子生非异也,善假于物也。”——荀子《劝学》。
美国好莱坞电影有《蜘蛛侠》、《蝙蝠侠》,无非就是让人具有了某种动物的能力,从而“能力越大、责任越大”,如果换成面向对象术语的话,就是“人”类继承了“蜘蛛”类和“蝙蝠”类,从而具有了它们的功能。按照这个思路发展,如果人类要想渡海过江,就得继承鱼类,人类要想远行,就得继承马类,于是电影《鲨鱼侠》、《赤兔侠》就横空出世了。如果让荀子去好莱坞当编剧的话,作为唯物主义思想家,才不相信人变马、人变鱼那些事呢!就大笔一挥:“君子生非异也,善假于物也”!人类要想远行?骑马!要想过江?乘船!不是让人成为(is-a)马和鱼,而是使用(has-a/use-a)马和船。荀老先生给我们揭示了面向对象中扩展功能的一个重要原则:优先使用委托而不是继承,正是桥梁模式所体现的原则。

桥梁模式是个比较难理解的模式,先看定义和结构图:

将抽象部分与它的实现部分分离,使得它们都可以独立地变化。

说实话,桥梁模式是我琢磨时间最长的一个模式,学习的时候经常陷于一头雾水之中。看定义,“抽象部分”、“实现部分”,比较拗口,也不明白;看结构图,两边各有一个基类,分别有它们的子类,基类之间是聚合引用关系,看的云山雾罩,还是蒙圈!

这个模式最让人迷惑的地方是“抽象”和“实现”。巧合地是,它们居然和面向对象中最经常说的抽象类、实现类居然一样的名字,大家第一次见到它就自然而然的就往这上面靠了,心理习惯,原本正常,但如果被它误导了,硬是按照这种先入为主的观念去分析这个模式,肯定要走弯路了。

怎样才能拨开云雾见真面目?

关键点之一:抽象角色和实现角色独立的变化

学习模式时,不一定要按照它的标准结构图研究,因为标准结构图是为了封装变化和抽象依赖,才定义了抽象基类。如果我们把基类看作是干扰元素,它们是为了实现依赖倒置原则而引入的,在应用时最终都要落实到具体对象上去,我们可以暂时忽略它们。在上图中,把Abstraction和Implementor类都看作是具体类(我们实际开发时,经常面对具体场景,喜欢用具体类),并且把它们的子类都去掉,那么结构图就剩下了两个类,一个类聚合了另一个类,这正是我们最常用的一种方式,在一个对象如A中引用另一个对象如B,在处理具体业务时,A会调用B中的方法,是不是最常见的方式?这是什么方式呢?就是类之间的组合关系,它还有一个名字:委托。为什么要使用抽象角色和实现角色的抽象类,还不是为了它们能够各自独立的变化,如果只使用具体类,当实现角色变化了,比如又新建了一个具体实现角色类,则抽象角色必须修改代码来应对这个变化。

抽象和实现可以独立的变化,所谓独立的变化,就是它们无论谁被改变了,都不会影响到对方。有变化就得封装变化,怎么封装?面向接口!也就是抽象依赖,依赖于抽象基类。从结构图中可以看到抽象角色和实现角色都有基类和子类,应用时无论是访问抽象角色的具体类还是访问实现角色的具体类,都是依赖于基类(依赖倒置原则)。显然只要接口稳定(它们的基类),各自的子类变化不会影响到对方,一个子类变化了,比如有新的子类,通过场景类创建它,通过基类接口传过去就可以了,子类没有影响(里氏替换原则)。

关键点之二:抽象角色和实现角色相分离

假设有一个桥梁类:Bridge1,它的组成有两个重要的方法:Abstraction1和Implementor1,为了实现业务,它们得一块配合实现功能,刚开始相安无事。后来随着业务的发展,发生了变化:需要Abstraction1和另一个Implementor2配合实现业务功能,于是定义了Bridge2并继承Bridge1,这样复用Abstraction1并实现Implementor2就行了。后来又要另一个Abstraction2和Implementor1配合,于是定义了Bridge3继承自Bridge1并实现Abstraction2就行了。变化总是存在的,后来又变化了,要求Abstraction2和Implementor2配合,于是又定义了Bridge4继承Bridge3,并实现了Implementor2,一直演化下去。。。。发现没有?Abstraction角色和Implementor角色经常变化而且是独立变化,每变化一次就得重新派生一个子类来扩展功能。

那么怎么封装这种变化呢?

那就是把这两个角色从Bridge类中拆分,让它们分别组成一个新类:Abstraction和Implementor,把原来的Bridge类保留Abstraction方法,并把类名改为Abstraction,把Implementor提炼出去形成一个新类:Implementor。然后为Abstraction提供一个方法能够引用Implementor实现功能,于是Abstraction和Implementor形成了委托关系,因为还有其它Abstraction1、2、3。。等和Implementor1、2、3。。。等,为了封装这些变化,分别为它们提供了抽象基类,于是最终演化成了这个桥梁模式。这样就能让Abstraction和Implementor独立变化了,并通过委托而不是继承来扩展功能,从这一点上来说,Abstraction和Implementor原本就是一个类的组成部分,只是为了封装变化而提炼出来,所以它是典型的结构型模式。

关键点之三:怎样理解定义里面的“抽象”和“实现”呢?

个人认为这里的抽象表示对象提供的功能,而实现表示的是实现功能时所用的手段、方式(说实话,我翻阅了很多博客文章,大都语焉不详,或者干脆绕过概念,直接上例子,我揣摩了很长时间,这个解释不一定很到位,但至少能更容易理解这个模式),名正则言顺,按照这个意思去分析桥梁模式,就顺利多了:抽象角色为了实现自己的功能,借助于委托对象来实现,再正常不过了。看起来那么高大上的模式,描述的居然是是最常见、最普通的场景,桥梁模式大隐隐于市,隐藏的太深了,只是我们都浑然不觉而已。

桥梁模式体现了面向对象设计一个非常重要的原则:扩展功能时,要优先使用委托,而不是继承(CARP原则),桥梁模式就是为了实现这个原则定义的,该模式的关键词:委托,也叫组合或聚合。

1、为什么优先使用委托而不是继承?

首先,因为继承是静态扩展,是基于类的,它必须通过调整类的结构才能扩展类的功能,当父类修改了,子类也得跟着全部编译,而且继承的关系是固定写死的,无法在运行时动态改变。而委托是动态扩展,是基于对象的,它是在运行时进行扩展的,当修改或者增加新的委托类时,只要把它的对象实例替换旧的对象实例即可。

正是因为继承不如委托灵活,所以都建议优先使用委托,而不是继承,不到万不得已,不要用继承(我们回忆一下,在编写让View处理事件的代码时,大家的选择是编写一个View的子类并重写它的onXXX()方法的时候多,还是直接调用View的setOnXXListener()方法来添加一个委托OnXXListener对象的时候多?委托灵活还是继承灵活,无需多言了吧)。

其次,防止类膨胀,也就是防止类数量太多,因为它是通过引用别的对象来扩展功能的,使用对象的功能而无需继承它,显然用不着定义新类了。例如,如果通过重写View的onXXX()方法来扩展View的功能,首先就得定义一个View的子类,而使用setOnXXListener来扩展功能,就无需定义View子类了,一目了然。

2、我们平时的编程实践中,在一个对象中委托另一个对象扩展功能,算是桥梁模式吗?

从要求抽象角色和实现角色独立的变化来看,我们平时使用的委托方式,算不上是桥梁模式。因为我们平时不注意抽象,或者认为类将来不会有大的变化,一般都是直接使用具体类(不符合依赖倒置原则),很少定义它们的基类,并依赖于基类。往往具体类和具体类是一一对应的关系,一个修改了,另一个肯定跟着修改,无法达到独立的变化,属于典型的基于对象编程。

Android中的桥梁模式应用:

在编程实践中,严格按照桥梁模式来开发程序时不常见,前面说过,我们平时经常面对的是具体业务场景,自然而然的使用具体的类来实现功能。让一个具体的类来委托另一个具体的类来实现功能,是最为常见使用场景,很少写出桥梁模式的代码来。不过在一些应用框架中,经常会使用一些标准的桥梁模式,目的是使模块之间能够灵活扩展和解耦,比如Android中就有一些典型的桥梁模式的应用。

下面分别分析一下:

1、AdapterView与Adapter:
对照桥梁模式的定义和结构图,我们发现AdapterView类是抽象角色,Adapter是实现角色,AdapterView所呈现的UI界面不是自己实现的,即它的子View是由Adapter决定的,也就是说AdapterView的职责是实现一组View的展现,但实现的具体方法要委托Adapter来提供。

我们知道,AdapterView有子类ListView、GridView、Spinner。。。等,使用时要给它set一个Adapter类型的对象,这个Adapter也是抽象类,也有一些子类,如ArrayAdapter、SimpleAdapter。。。等。在实际使用时,我们会创建一个具体的XXXAdapter对象,让ListView、GridView等使用,它们之间是独立变化的,没有互相影响,确实是桥梁模式应用。

2、Window与View:

Android中所有的视图View都是通过Window来负责展现的,无论是Activity、Toast、Dialog还是PopupWindow,最终都是要attach到Window上的,Window是View的直接管理者。从Window类的注释可以看出,它是一个顶级窗口外观和行为的抽象基类,提供了标准的UI策略,如背景、标题栏、缺省按键处理等,而响应用户显示操作的由内容View负责处理。

Window对象与View之间是如何协作的呢?就以Activity来说吧,Activity要显示UI界面时,首先要获得一个Window对象,并把各个View控件添加到Window中去。在实际开发时,Activity给它什么View,它就显示什么View。Window把如何显示内容UI(即content view)全部委托给了View类,而View又有一系列的Layout、View、Widget等众多子类,Window类也有子类,它们都可以独立的变化,典型的桥梁模式应用,即Window是抽象角色,而View是实现角色。我们在开发时,可以在Activity中随意创建布局,并把布局作为参数调用setContentView(),只修改Activity就行了,对Window没有半毛影响。

按照桥梁模式的原理,按说Activity也能随意创建一个Window对象,对ContentView里面的View也没半毛关系。不过在当前android的SDK中,Window只有一个子类,即PhoneWindow,这样就成了抽象角色只有一个具体类,Activity也没得选择,不能独立变化,只能使用这个唯一的子类。我们先看一下Window里面的说明:The only existing implementation of this abstract class is android.policy.PhoneWindow, which you should instantiate when needing a Window. Eventually that class will be refactored and a factory method added for creating Window instances without knowing about a particular implementation.大意是Window目前只实现了一个子类PhoneWindow,将来会重构这个类,并使用工厂方法来创建具体的Window对象,至于创建什么样的对象,调用者就不用关注了,工厂会按照一定的场景创建合适的Window对象。这就不得不让我们有这样的思考:比如,如果将来Android手机、AndroidPad和AndroidTV的UI整体界面发生了很大的变化,那么就可以定义一个Window的子类来适应新场景了,比如定义子类PadWindow类用于Pad设备,定义子类TvWindow类用于电视设备,而PhoneWindow只用于手机。工厂方法根据所用设备的类型创建不同的Window对象实例就行了,对市面上已经存在的App应用没有任何影响,同样的View在不同的Window对象上会有不同的展现,也就是说通过该模式已经为将来可能产生的变化做了封装,这也正是Android框架的优秀之处,体现了它的前瞻性。当然这纯属个人猜测,Android框架以后是否会按照上面分析的来演进,拭目以待吧。

3、IInterface与IBinder:

Binder中的IBinder与IInterface也是标准的桥梁模式:IInterface是抽象角色,负责业务逻辑,IBinder是实现角色,负责RPC通信逻辑。IInterface定义了业务逻辑的接口方法,它的具体子类实现了客户端(Proxy)和服务端(Stub)的业务逻辑,但是要想实现客户端-服务器端的RPC通信功能,还得委托IBinder对象来完成。查看IInterface接口的定义,它只有一个唯一接口方法:asBinder(),返回一个IBinder对象来委托使用,不过,在开发RPC的业务模块时,会根据AIDL定义的接口,使用工具会创建一个具体的IInterface子类来包含这些业务接口方法。

不过,当前IBinder的唯一实现是Binder类,还没有别的子类,也就是说实现角色只有一个子类。发现没有?恰恰和前面的Window-View相反,它们是抽象角色Window只有一个子类,这里是实现角色只有一个子类。

我们不妨脑洞大开一下:Google将来有没有可能定义一个新的子类来实现IBinder接口负责RPC通信?

比如通过socket方式,并按照Binder的协议来实现,假设名字就叫SocketBinder,这样可以通过网络实现RPC,就把Binder的服务框架从本地设备的不同进程之间扩展到了不同设备的进程之间。毕竟现在应用于不同场所的android设备越来越多,完全可以在手机、电视、可穿戴设备之间进行RPC调用,请求驻留在其它设备中的应用服务,比如DLNA就提供了这样的功能,不过它是使用SOAP基于HTTP来实现的RPC,Google完全可以在Binder框架上实现这样的功能。如果将来真的这样做了,对现有的Binder框架代码,不会有大的改动,应用层更是没有任何变化;android系统的相关功能模块,比如ServiceManager模块,只要根据部署远程服务的设备提供对应的IBinder子类对象并传给IInterface对象就行了:如果是部署在本地的服务,就使用本地Binder对象;如果是部署在其它设备上的服务,就使用SocketBinder对象,对已有的IInterface对象不会任何的修改。当然如果那样做,ServiceManager还要有服务注册、服务发现等业务的改动,那是其它范畴了,就不展开讨论了。

Android中的设计模式-桥梁模式相关推荐

  1. Android中的设计模式-状态模式

    原文出处:http://www.linuxidc.com/Linux/2015-04/116013.htm 状态模式说明 "状态模式允许一个对象在其内部状态改变的时候改变其行为.这个对象看上 ...

  2. 说说设计模式~桥梁模式(Bridge)

    在软件系统中,某些类型由于自身的逻辑,它具有两个或多个维度的变化,那么如何应对这种"多维度的变化"?如何利用面向对象的技术来使得该类型能够轻松的沿着多个方向进行变化,而又不引入额外 ...

  3. 寻找android中的设计模式(三)

     寻找android中的设计模式(三) 寻找工厂模式 工厂模式的家族分四种:静态工厂模式.简单工厂模式.工厂方法模式.抽象工厂模式. 下面以开冒菜店为例,假设我定义了一家冒菜店: <pre ...

  4. [Head First设计模式]餐馆中的设计模式——命令模式

    系列文章 [Head First设计模式]山西面馆中的设计模式--装饰者模式 [Head First设计模式]山西面馆中的设计模式--观察者模式 [Head First设计模式]山西面馆中的设计模式- ...

  5. .Net中的设计模式——Iterator模式

    在.Net中,我们很少有机会使用Iterator模式,因为.Net Framework已经运用Iterator模式为所有的集合对象实现了迭代器.我们在遍历集合对象时,喜欢使用C#提供的foreach语 ...

  6. [Head First设计模式]饺子馆(冬至)中的设计模式——工厂模式

    系列文章 [Head First设计模式]山西面馆中的设计模式--装饰者模式 [Head First设计模式]山西面馆中的设计模式--观察者模式 [Head First设计模式]山西面馆中的设计模式- ...

  7. 工作中的设计模式 —— 策略模式

    前言 返利网站 https://m.cpa5.cn/ 策略模式是一种行为设计模式,它能让你定义一系列算法,并将每种算法分别放入独立的类中,以使算法的对象能够相互替换. 使用场景 策略模式在工作中使用的 ...

  8. android工厂模式源码,Android源码设计模式——工厂模式

    工厂模式也是为了构建一个新的对象,它是创建型模式的一种. Android源码设计模式--Build模式(应用:AlertDialog源码分析) 上述是之前的Build模式,也是创建型模式一种,不懂的小 ...

  9. java设计模式-桥梁模式

    桥梁模式 桥梁模式是对象的结构模式.又称为柄体(Handle and Body)模式或接口(Interface)模式.桥梁模式的用意是"将抽象化(Abstraction)与实现化(Imple ...

最新文章

  1. 号称史上最牛逼的几篇博客整理(python+java+js等)
  2. SyntaxError: Non-UTF-8 code starting with ‘\xe4‘ in file解决办法
  3. 干货:阅读跟踪 Java 源码的几个小技巧!
  4. System.nanoTime
  5. Commons codec jar包详解
  6. php本地服务手机适配器,php适配器模式(adapter pattern)
  7. map.addoverlay php,覆盖物 - 百度地图开发文档 - php中文网手册
  8. 怎么用git将本地代码上传到远程服务器_git在远程服务器创建项目并将本地代码推送到服务器上...
  9. 对软件工程这门课的期望
  10. SSI指令使用详解(转)
  11. C# list删除 另外list里面的元素_python学习笔记第三课:List(列表)
  12. Java将视频文件、图片文件转Base64编码
  13. 楷书书法规则_书法结构|楷书10大结构原则详解之3--主笔突出原则
  14. 使用pyquery爬取搜狗微信文章
  15. VBA 中Dim含义
  16. 什么是分布式系统,这么讲不信你不会
  17. 014基于深度学习的脑电癫痫自动检测系统-2018(300引用)
  18. (2021年)IT技术分享社区个人文章汇总(编程技术篇)
  19. Android 透明状态栏
  20. webfunny埋点漏斗功能

热门文章

  1. 消防设备电源监控系统在城市建筑中的应用
  2. 学计算机能进入建行工作总结,建设银行员工年终个人工作总结
  3. C++volatile关键字
  4. 世界上战争多发的原因
  5. 修改apiserver证书
  6. ajax then fail done,Jquery $when done then的用法详解
  7. 一起做RGB-D SLAM(8) (关于调试与补充内容)
  8. 网络安全 payload、shellcode、exp、poc
  9. qsort源代码分析
  10. 单向可控硅烧G极电阻,MCR22-8