23 设计模式——桥接模式

返回设计模式博客目录

介绍


桥接(Bridge)模式:将抽象部分与实现部分分离,使它们都可以独立的变化

在现实生活中,某些类具有两个或多个维度的变化,如图形既可按形状分,又可按颜色分。如何设计类似于 Photoshop 这样的软件,能画不同形状和不同颜色的图形呢?如果用继承方式,m 种形状和 n 种颜色的图形就有 m×n 种,不但对应的子类很多,而且扩展困难。

当然,这样的例子还有很多,如不同颜色和字体的文字、不同品牌和功率的汽车、不同性别和职业的男女、支持不同平台和不同文件格式的媒体播放器等。如果用桥接模式就能很好地解决这些问题。

当一个类存在两个独立变化的纬度,且这两个纬度都需要进行扩展,我们可以使用桥接模式。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。

优点

  • 由于抽象与实现分离,所以扩展能力强;
  • 其实现细节对客户透明。

缺点

由于聚合关系建立在抽象层,要求开发者针对抽象化进行设计与编程,这增加了系统的理解与设计难度。

使用场景

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

结构与实现


可以将抽象化部分与实现化部分分开,取消二者的继承关系,改用组合关系。

模式包含以下主要角色。

  • Abstraction(抽象部分):定义抽象类,并包含一个对实现化对象的引用。
  • RefinedAbstraction(扩展抽象部分):是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
  • Implementor(实现部分):定义实现化角色的接口,供扩展抽象化角色调用。
  • ConcreteImplementor(实现部分的具体实现):给出实现化角色接口的具体实现。

其结构图如下图所示。

Abstraction 和 Implementor 就是两个独立纬度变化的类,Implementor 相对于 Abstraction 是一个聚合的关系,也就是 Abstraction 可能拥有多个 Implementor,模板代码如下:

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
public class BridgeTest {
public static void main(String[] args) {
Implementor implementor = new ConcreteImplementor();
Abstraction abs = new RefinedAbstraction(implementor);
abs.operation();
}
}
// 实现化角色
interface Implementor {
void operationImpl();
}
// 具体实现化角色
class ConcreteImplementor implements Implementor {
public void operationImpl() {
System.out.println("具体实现化(Concrete Implementor)角色被访问" );
}
}
// 抽象化角色
abstract class Abstraction {
protected Implementor implementor;
protected Abstraction(Implementor implementor) {
this.implementor = implementor;
}
public abstract void operation();
}
// 扩展抽象化角色
class RefinedAbstraction extends Abstraction {
protected RefinedAbstraction(Implementor implementor) {
super(implementor);
}
public void operation() {
System.out.println("扩展抽象化(Refined Abstraction)角色被访问" );
implementor.operationImpl();
}
}

示例


去咖啡馆喝咖啡一般分为 4 种,大杯加糖,大杯不加糖,小杯加糖和小杯不加糖。对于大杯和小杯,加糖和不加糖其实是两个相对独立纬度的变化。面先定一个咖啡类:

1
2
3
4
5
6
7
8
9
10
11
12
public abstract class Coffee {
protected CoffeeAdditives impl;
public Coffee(CoffeeAdditives impl) {
this.impl = impl;
}
/**
* 咖啡具体是什么样的由子类决定
*/
public abstract void makeCoffee();
}

CoffeeAdditives 是一种桥接的方式,咖啡分为大杯和小杯,下面继续看看大杯咖啡和小杯咖啡的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class LargeCoffee extends Coffee{
public LargeCoffee(CoffeeAdditives impl) {
super(impl);
}
@Override
public void makeCoffee() {
System.out.println("大杯的"+impl.addSomething()+"咖啡");
}
}
public class SmallCoffee extends Coffee {
public SmallCoffee(CoffeeAdditives impl) {
super(impl);
}
@Override
public void makeCoffee() {
System.out.println("小杯的"+impl.addSomething()+"咖啡");
}
}

至于加糖不加糖我们通过 CoffeeAdditives 这种桥接方式定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public abstract class CoffeeAdditives {
public abstract String addSomething();
}
public class Ordinary extends CoffeeAdditives {
@Override
public String addSomething() {
return "原味";
}
}
public class Sugar extends CoffeeAdditives {
@Override
public String addSomething() {
return "加糖";
}
}

最终调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Test {
public static void main(String[] args){
// 原汁原味
Ordinary ordinary = new Ordinary();
// 准备糖类
Sugar sugar = new Sugar();
// 大杯咖啡 原味
LargeCoffee largeCoffeeOrdinary = new LargeCoffee(ordinary);
largeCoffeeOrdinary.makeCoffee();
// 小杯咖啡 原味
SmallCoffee smallCoffeeOrdinary = new SmallCoffee(ordinary);
smallCoffeeOrdinary.makeCoffee();
// 大杯咖啡 加糖
LargeCoffee largeCoffeeSugar = new LargeCoffee(sugar);
largeCoffeeSugar.makeCoffee();
// 小杯咖啡 加糖
SmallCoffee smallCoffeeSugar = new SmallCoffee(sugar);
smallCoffeeSugar.makeCoffee();
}
}

如果此时咖啡馆为了满足更多人的习惯,推出中杯的咖啡怎么办呢?对于本例来说,这种需求的变化其实就是 Coffee 类的变化,定义中杯扩展类 MiddleCoffee 类即可。

1
2
3
4
5
6
7
8
9
10
public class MiddleCoffee extends Coffee {
public MiddleCoffee(CoffeeAdditives impl) {
super(impl);
}
@Override
public void makeCoffee() {
System.out.println("中杯的"+impl.addSomething()+"咖啡");
}
}

同样地,为了增加咖啡类口味的种类,我们也可以让 CoffeeAdditives 类变化起来,增加更多的子类表示,诸如加奶、加蜂蜜等。

ANDROID 源码中的实现


比较典型的是 Window 与 WindowManager 之间的关系,它们就用到了桥接这种模式。它们的关系如下图所示:

在 framework 中 Window 和 PhoneWindow 构成窗口的抽象部分,其中 Window 类为该抽象部分的抽象接口,PhoneWindow 为抽象部分具体的实现及扩展。而 WindowManager 则为实现部分的基类,WindowManagerImpl 为实现部分具体的逻辑实现,其使用 WindowManagerGlobal 通过 IWindowManager 接口与 WindowManagerService 进行交互(简称 WMS),并由 WMS 完成具体的窗口管理工作. 如下是 Window 与 WindowManager 桥梁搭建的主要代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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) {
mAppToken = appToken;
mAppName = appName;
mHardwareAccelerated = hardwareAccelerated
|| SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
// 代码省略...
}