GoF 的《设计模式》 中,桥接模式是这么定义的:Decouple an abstraction from its implementation so that the two can vary independently。(将抽象和实现解耦,让它们可以独立变化。)
其他资料的解释:一个类存在两个(或多个)独立变化的维度,我们通过组合的方式,让这两个(或多个)维度可以独立进行扩展。 通过组合关系来替代继承关系,避免继承层次的指数级爆炸。这种理解方式非常类似于,我们之前讲过的“组合优于继承”设计原则。
1 桥接模式举例 举个栗子🌰
我们点咖啡的时候,可以选小杯,大杯,可以加糖,或原味。那用设计模式怎么灵活装配这些需求呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public abstract class Coffee { protected CoffeeAdditives impl; public Coffee (CoffeeAdditives impl) { this .impl = impl; } public abstract void makeCoffee () ; } public class LatgeCoffee extends Coffee { public LatgeCoffee (CoffeeAdditives impl) { super (impl); } @Override public void makeCoffee () { System.out.println("大杯的" + impl + "咖啡" ); } } public class SmallCoffee extends Coffee { public SmallCoffee (CoffeeAdditives impl) { super (impl); } @Override public void makeCoffee () { System.out.println("小杯的" + impl + "咖啡" ); } }
Coffee 类中保持了对 CoffeeAdditives 的引用,以便调用具体的实现。同样的,咖啡还分大杯小杯,定义两个子类继承于 Coffee 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public abstract class CoffeeAdditives { public abstract String addSomething () ; } public class Sugar extends CoffeeAdditives { @Override public String addSomething () { return "加糖" ; } } public class Ordinary extends CoffeeAdditives { @Override public String addSomething () { return "原味" ; } }
这里的 CoffeeAdditives 其实实现部分,而 Coffee 则对应于抽象部分,模式定义中所谓的 “抽象” 与 “实现” 实质上对应的是两个独立变化的维度,因此,任何多维度变化类或者说多个树状类之间的耦合都可以使用桥接模式来实现解耦。
Coffee 类虽是一个抽象类,但它并非是所谓的 “抽象部分” ,而 CoffeeAdditives 类也并非一定就是 “实现部分” ,两者各自为一维度,独立变化,仅此而已,所谓的 “抽象与实现分离” 更偏向于我们实际的程序开发,两者并不一定挂钩,这里其实就可以看到桥接模式的应用性其实很广泛,并不局限于程序设计。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Ordinary implOrdinary = new Ordinary(); Sugar implSugar = new Sugar(); LatgeCoffee latgeCoffeeOrdinary = new LatgeCoffee(implOrdinary); latgeCoffeeOrdinary.makeCoffee(); SmallCoffee smallCoffeeOrdinary = new SmallCoffee(implOrdinary); smallCoffeeOrdinary.makeCoffee(); LatgeCoffee latgeCoffeeSugar = new LatgeCoffee(implSugar); latgeCoffeeSugar.makeCoffee(); SmallCoffee smallCoffeeSugar = new SmallCoffee(implSugar); smallCoffeeSugar.makeCoffee();
这时候如果需要新增 中杯 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class MiddleCoffee extends Coffee { public MiddleCoffee (CoffeeAdditives impl) { super (impl); } @Override public void makeCoffee () { System.out.println("中杯的" + impl + "咖啡" ); } } MiddleCoffee middleCoffeeSugar = new MiddleCoffee(implSugar); middleCoffeeSugar.makeCoffee(); MiddleCoffee middleCoffeeOrdinary = new MiddleCoffee(implOrdinary); middleCoffeeOrdinary.makeCoffee();
我也可以让 CoffeeAdditives 类变化起来,比如加奶、加蜂蜜。不管是 Coffee 变化了还是 CoffeeAdditives 变化了,其相对于对方而言都是独立的没有什么过多的交集,两者之间唯一的联系就是 Coffee 中保持的对 CoffeeAdditives 的引用,此乃两者之纽带,这就是桥接模式。
2 Android 源码中的桥接模式 首先是 Adapter 与 AdapterView AdapterView 与 Adapter
这里引用原文两张图片,Adapter 对应抽象部分,Adapter对应实现部分。
还有是 View 的视图层级中,CheckBox、CompoundButton、Button、TextView 和 View 之间构成一个继承关系的视图层级,每一层视图都仅仅是对一种类型控件的描述,其定义了该类控件所拥有的基本属性和行为,但是将它们真正绘制到屏幕的部分是由与 View 相关的功能实现类 DisplayList 、HardwareLayer 和 Canvas 负责。
还有就是 Window 和 WindowManager
在 framework 中 Window 和 PhoneWindow 构成窗口的抽象部分,其中 Window 类为该抽象部分的抽象接口,PhoneWindow 为抽象部分具体的实现及扩展。而 WindowManager 则为实现部分的基类,WindowManagerlmpl 为实现部分具体的逻辑实现,其使用 WindowManagerGlobal 通过 IWindowManager 接口与WindowManagerService(也就是我们常说的 WMS )进行交互,并由 WMS 完成具体的窗口管理工作,如下为 Window 与 WindowManager 桥梁搭建的主要代码,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public abstract class Window { public void setWindowManager (WindowManager wm, IBinder appToken, String appName) { setWindowManager(wm, appToken, appName, false ); } public void setWindowManager (WindowManager wm, IBinder appToken, String appName, boolean hardwareAccelerated) { if (wm == null ) { wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); } mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this ); } }
可以毫不夸张地说,Android 的 framework 层主要就是由它与另外一个系统服务 ActivityManagerService(简称 AMS )还有 View 所构成,这 3 个模块穿插交互在整个 framework 中,掌握了它们之间的关系以及每一个步骤的逻辑,你对 framework 就至少了解百分之五十了。
WindowManager启动篇
3 桥接模式 Android 实战 View 的视图层级与执行真正的硬件绘制相关类之间的关系可以看作是一种桥接模式,其实这里也可以模仿这种行为来让我们的自定义控件以桥接的方式提供多种不同的实现机制,逻辑很简单,这里以进度条为例,鉴于进度条的逻辑并不复杂,我们可以自己继承View类来实现进度条控件,这里就以自定义水平、垂直和圆形 3 种不同的进度条为例,为了后期扩展,我们以一个实现类来声明不同进度条需要实现的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public abstract class BaseProgressBar { public static final int HORIZONTAL = 0 ; public static final int VERTICAL = 1 ; public static final int CIRCLE = 2 ; protected Paint mPaint; protected BaseProgressBar () { mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); } public abstract int getMeasureHeight () ; public abstract int getMeasureWidth () ; public abstract void draw (View view, Canvas canvas) ; }
具体实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 public class CircleProgressBar extends BaseProgressBar { @Override public int getMeasureHeight () { return 50 ; } @Override public int getMeasureWidth () { return 1000 ; } @Override public void draw (View view, Canvas canvas) { } } public class HorizontalProgressBar extends BaseProgressBar { @Override public int getMeasureHeight () { return 50 ; } @Override public int getMeasureWidth () { return 1000 ; } @Override public void draw (View view, Canvas canvas) { } } public class VerticalProgressBar extends BaseProgressBar { @Override public int getMeasureHeight () { return 50 ; } @Override public int getMeasureWidth () { return 1000 ; } @Override public void draw (View view, Canvas canvas) { } }
然后用一个继承于 View 的进度条来分派绘制逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 public class ProgressBar extends View { private static final int HEIGHT = 0x6846 , WIDTH = 0x7889 ; private BaseProgressBar mBaseProgressBar; private int style = BaseProgressBar.CIRCLE; public ProgressBar (Context context) { this (context, null ); } public ProgressBar (Context context, @Nullable AttributeSet attrs) { this (context, attrs, 0 ); } public ProgressBar (Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super (context, attrs, defStyleAttr); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ProgressBar, defStyleAttr, 0 ); style = a.getInt(R.styleable.ProgressBar_style, BaseProgressBar.CIRCLE); if (style == BaseProgressBar.HORIZONTAL) { mBaseProgressBar = new HorizontalProgressBar(); } else if (style == BaseProgressBar.VERTICAL) { mBaseProgressBar = new VerticalProgressBar(); } else { mBaseProgressBar = new CircleProgressBar(); } a.recycle(); } @Override protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) { super .onMeasure(widthMeasureSpec, heightMeasureSpec); setMeasuredDimension(measureDimension(WIDTH, widthMeasureSpec), measureDimension(HEIGHT, heightMeasureSpec)); } private int measureDimension (int type, int measureSpec) { if (style == BaseProgressBar.CIRCLE) { return mBaseProgressBar.getMeasureWidth(); } int result; int mode = MeasureSpec.getMode(measureSpec); int size = MeasureSpec.getSize(measureSpec); if (mode == MeasureSpec.EXACTLY) { result = size; } else { if (type == HEIGHT) { result = mBaseProgressBar.getMeasureHeight(); } else { result = mBaseProgressBar.getMeasureWidth(); } if (mode == MeasureSpec.AT_MOST) { result = Math.min(result, size); return result; } } return result; } @Override protected void onDraw (Canvas canvas) { super .onDraw(canvas); mBaseProgressBar.draw(this , canvas); } }
这里的 ProgressBar 类持有 BaseProgressBar 类的引用,通过在不同的绘制阶段调用不同进度条实现类的相应方法完成具体的逻辑。如本文所说,桥接模式的应用其实非常的广泛,除了上面我们提到的控件例子外,在进行数据库 dao 类设计时我们有时也会需要使用到桥接模式。 而且对于 Android 来说,应用层与 Native 层之间的交互就是一个最好的例子,在需要操纵 Android 设备硬件时就需要使用一个连接应用层与 Native 的桥梁,这个桥梁通常是一个具体的类,比如提供操作相机的 Camera 、播放音视频的 MediaPlayer 、提供图形绘制接口的 OpenCV 等,这些 API 类为我们操作底层硬件提供了可能。
总结 对于这个模式有两种不同的理解方式。在 GoF 的《设计模式》一书中,桥接模式被定义为:
“将抽象和实现解耦,让它们可以独立变化。”
在其他资料和书籍中,还有另外一种更加简单的理解方式:
“一个类存在两个(或多个)独立变化的维度,我们通过组合的方式,让这两个(或多个)维度可以独立进行扩展。”
对于第一种 GoF 的理解方式,弄懂定义中 “抽象” 和 “实现” 两个概念,是理解它的关键。
定义中的 “抽象” ,指的并非 “抽象类” 或 “接口” ,而是被抽象出来的一套 “类库” ,它只包含骨架代码,真正的业务逻辑需要委派给定义中的 “实现” 来完成。而定义中的 “实现” ,也并非 “接口的实现类” ,而是一套独立的 “类库” 。“抽象” 和 “实现” 独立开发,通过对象之间的组合关系,组装在一起。
感谢
设计模式之美
《Android 源码设计模式解析与实战》
以及上文中的链接