这篇博客我们来介绍一下桥接模式(Bridge Pattern),它也是结构型设计模式之一。桥接,顾名思义,就是用来连接两个部分,使得两个部分可以互相通讯或者使用,桥接模式的作用就是为被分离了的抽象部分和实现部分搭桥。在现实生活中也有很多这样的例子,一个物品在搭配不同的配件时会产生不同的动作和结果,例如一辆赛车搭配的是硬胎或者是软胎就能够在干燥的马路上行驶,而如果要在下雨的路面行驶,就需要搭配雨胎了,这种根据行驶的路面不同,需要搭配不同的轮胎的变化的情况,我们从软件设计的角度来分析,就是一个系统由于自身的逻辑,会有两个或多个维度的变化,有时还会形成一种树状的关系,而为了应对这种变化,我们就可以使用桥接模式来进行系统的解耦。
桥接模式,作用是将一个系统的抽象部分和实现部分分离,使它们都可以独立地进行变化,对应到上面就是赛车的种类可以相对变化,轮胎的种类可以相对变化,形成一种交叉的关系,最后的结果就是一种赛车对应一种轮胎就能够成功产生一种结果和行为。
转载请注明出处:http://blog.csdn.net/self_study/article/details/51622243。
PS:对技术感兴趣的同鞋加群544645972一起交流。
java/android 设计模式学习笔记目录
将抽象部分与实现部分分离,使他们都可以独立地进行变化。为了达到让抽象部分和实现部分独立变化的目的,抽象部分会拥有实现部分的接口对象,有了实现部分的接口对象之后,就能够通过这个接口来调用具体实现部分的功能。桥接在程序上就体现成了抽象部分拥有实现部分的接口对象,维护桥接就是维护这个关系,也就是说,桥接模式中的桥接是一个单方向的关系,只能够抽象部分去使用实现部分的对象,而不能反过来。
桥接模式适用于以下的情形:
我们来看看桥接模式的 uml 图:
桥接模式也分为四个角色:
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 不能在子线程中更新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 的系统进行拆分,减少类的数量。桥接模式适合使用在需要跨越多个平台的图形和窗口系统上,优点很明显:
这几个都是结构型设计模式,他们有些类似,在实际使用过程中也容易搞混,我们在这就给他们做一个对比:
适配器模式和其他三个设计模式一般不容易搞混,它的作用是将原来不兼容的两个类融合在一起,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