桥接模式


# 桥接模式

# 介绍

桥接模式(Bridge Pattern)又称桥梁模式,将抽象部分与它的实现部分分离,使它们都可以独立地变化。使用组合关系代替继承关系,降低抽象和实现两者间的耦合度。

抽象部分和实现部分可能不太好理解,举个例子,香蕉、苹果、西瓜,它们共同的抽象部分就是水果,可以吃,实现部分就是不同的水果实体。再比如黑色手提包、红色钱包、蓝色公文包,它们共同的抽象部分是包和颜色,这部分的共性就可以被作为抽象提取出来。

# 通俗的示例

比如包包,包的种类是一个维度(钱包、书包、手提包),包的尺寸是一个维度(小号、中号、大号),包的颜色又是一个维度。每个维度可以自由变化后组合在一起形成一个包。

我们在系统设计时,如果给每个种类对应的每个尺寸和颜色都设置一个类,那么系统中的类就会很多。如果根据实际需要对种类、尺寸、颜色这些维度进行组合,那么将大大减少系统中类的个数。

将抽象和实现分离,互相独立互不影响:

  • 装饰不影响原有的功能,原有功能可以照常使用。
  • 产品有多个维度,每个维度都可以独立变化(实例化过程),包包这个例子的维度就是种类、尺寸、颜色,这几个维度可以独立地进行变化,从而组装成不同的包包。

# 桥接模式的通用实现

实现一下上面这个包包的例子:

/* 包包 */
class Bag {
  constructor(type, size, color) {
    this.type = type;
    this.size = size;
    this.color = color;
  }

  /* 展示 */
  show() {
    console.log(
      this.color.show() + this.size.show() + this.type.show()
    )
  }
}

/* 皮包类型 */
class BagType {
  constructor(type) {
    this.type = type;
  }

  show() {
    return this.type;
  }
}

/* 皮包尺寸 */
class BagSize {
  constructor(size) {
    this.size = size;
  }

  show() {
    return this.size;
  }
}

/* 皮包颜色 */
class BagColor {
  constructor(color) {
    this.color = color;
  }

  show() {
    return this.color;
  }
}


/* 抽象实例化 */
const walletType = new BagType('钱包');
const briefcaseType = new BagType('手提包');
const smallSize = new BagSize('小号');
const mediumSize = new BagSize('中号');
const redColor = new BagColor('红色');
const yellowColor = new BagColor('黄色');


const bagA = new Bag(walletType, smallSize, redColor);
bagA.show()
// 输出:红色小号钱包

const bagB = new Bag(briefcaseType, mediumSize, yellowColor);
bagB.show()
// 输出:黄色中号手提包
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
66

可以看到由于包包的结构被分别抽象为几个部件的组合,部件的实例化是在部件类各自的构造函数中完成,因此部件之间的实例化不会相互影响,新产品的创建也变得容易,这就是桥接模式的好处。

# 桥接模式的原理

我们可以提炼一下桥接模式,包包是产品(Product),种类、尺寸、颜色属于抽象出来的部件种类(Components),也属于独立的维度,而具体的部件钱包、小号、红色等属于部件实例(Instances),这些实例可以沿着各自维度变化,共同组成对应产品。主要有以下几个概念:

  • Product:产品,由多个独立部件组成的产品
  • Component:部件,组成产品的部件类
  • Instance:部件类的实例

结构如下:

桥接模式结构图

(桥接模式结构图)

# 桥接模式的实际应用

在某一个开发场景,产品经理提出需求:

  • 一个按钮的前景色默认为黑色、背景色为浅灰色。
  • 当光标 mouseover 的时候改变前景色为蓝色、背景色为绿色、尺寸变为 1.5 倍。
  • 当光标 mouseleave 的时候还原前景色、背景色、尺寸。
  • 在鼠标按下的时候前景色变为红色、背景色变为紫色、尺寸变为 0.5 倍。
  • 抬起后恢复原状。

我们要如何去实现呢?按照通常的思维方式开始码代码:

var btn = document.getElementById('btn')

// 设置监听事件
btn.addEventListener('mouseover', function() {
  btn.style.setProperty('color', 'blue');
  btn.style.setProperty('background-color', 'green');
  btn.style.setProperty('transform', 'scale(1.5)');
})

btn.addEventListener('mouseleave', function() {
  btn.style.setProperty('color', 'black');
  btn.style.setProperty('background-color', 'lightgray');
  btn.style.setProperty('transform', 'scale(1)');
})

btn.addEventListener('mousedown', function() {
  btn.style.setProperty('color', 'red');
  btn.style.setProperty('background-color', 'purple');
  btn.style.setProperty('transform', 'scale(.5)');
})

btn.addEventListener('mouseup', function() {
  btn.style.setProperty('color', 'black');
  btn.style.setProperty('background-color', 'lightgray');
  btn.style.setProperty('transform', 'scale(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

经理!我搞定了!

但是我们可以使用桥接模式来改造一下:把 DOM 对象的前景色、背景色作为其外观部件,尺寸属性是另一个尺寸部件,这样的话对各自部件的操作可以作为抽象被提取出来,使得对各自部件可以独立且方便地操作:

var btn = document.getElementById('btn');

/* 设置前景色和背景色 */
function setColor(element, color='black', bgc='lightgray') {
  element.style.setProperty('color', color);
  element.style.setProperty('background-color', bgc);
}

/* 设置尺寸 */
function setSize(element, size='1') {
  element.style.setProperty('transform', `scale(${ size })`);
}

// 设置监听事件
btn.addEventListener('mouseover', function() {
  setColor(btn, 'blue', 'green');
  setSize(btn, '1.5');
})

btn.addEventListener('mouseleave', function() {
  setColor(btn);
  setSize(btn);
})

btn.addEventListener('mousedown', function() {
  setColor(btn, 'red', 'purple');
  setSize(btn, '.5');
})

btn.addEventListener('mouseup', function() {
  setColor(btn);
  setSize(btn);
})
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

是不是看起来清晰多了,这里的 setColorsetSize 就是桥接函数,是将 DOM (产品)及其属性(部件)连接在一起的桥梁,用户只要给桥接函数传递参数即可,十分便捷。其他 DOM 要有类似的对外观部件和尺寸部件的操作,也可以方便地进行复用。

# 设计原则验证

  • 抽象和实现分离,解耦
  • 符合开放封闭原则

# 桥接模式的优缺点

优点:

  • 分离了抽象和实现部分,将实现层(DOM 元素事件触发并执行具体修改逻辑)和抽象层( 元素外观、尺寸部分的修改函数)解耦,有利于分层
  • 提高了可扩展性,多个维度的部件自由组合,避免了类继承带来的强耦合关系,也减少了部件类的数量。
  • 使用者不用关心细节的实现,可以方便快捷地进行使用。

缺点:

  • 桥接模式要求两个部件没有耦合关系,否则无法独立地变化,因此要求正确的对系统变化的维度进行识别,使用范围存在局限性。
  • 桥接模式的引入增加了系统复杂度。

# 桥接模式的适用场景

  • 如果产品的部件有独立的变化维度,可以考虑桥接模式。
  • 不希望使用继承,或因为多层次继承导致系统类的个数急剧增加的系统。
  • 产品部件的粒度越细,部件复用的必要性越大,可以考虑桥接模式。

# 其他相关模式

# 桥接模式和策略模式

  • 桥接模式:复用部件类,不同部件的实例相互之间无法替换,但是相同部件的实例一般可以替换。
  • 策略模式:复用策略类,不同策略之间地位平等,可以相互替换。

# 桥接模式与模板方法模式

  • 桥接模式:将组成产品的部件实例的创建,延迟到实例的具体创建过程中。
  • 模版方法模式:将创建产品的某一步骤,延迟到子类中实现。

# 桥接模式与抽象工厂模式

这两个模式可以组合使用,比如部件类实例的创建可以结合抽象工厂模式,因为部件类实例也属于一个产品类簇,明显属于抽象工厂模式的适用范围,如果创建的部件类不多,或者比较简单,也可以使用简单工厂模式。

(完)