0%

设计模式之Bridge模式

桥接(Bridge)是用于把抽象化与实现化解耦,使得二者可以独立变化。

介绍

桥接模式是一种将类的功能层次和实现层次分离的技术,所谓类的功能层次指的是类要实现什么功能,要定义多少个函数进行处理,在功能之中我们会用到继承来定义新的方法同时也能使用父类的方法,这样就构成了一个层次“父类-子类-孙类…”,这就是功能层次,与之对应的就是实现层次了,其实也很好理解,如果我们事先确定了完成一件事情的最基本的子功能,那么我们定义这些子功能为接口或者抽象类,然后使用实现类来进行实现,这样一个抽象类,多个实现类,(抽象类——>(实现类1,实现类2,实现类…))这样的结构就是实现层次,可以看到高度一直为2,而功能层次高度为N(N为继承的层次)。那么可不可以将这两者分离呢,一方面我们知道一个类的基础子功能并且能够使用到这些子功能的实现,另一方面我们可以在这些子功能的基础上定义出我们自己需要的功能(功能层次),如果可以的话,基本的元素就相当于空气、水分等元素,而我们需要的功能就是将这些东西组成一种种不同的物质。这里就用到了委托,或者说是组合。实现了功能层次和实现层次分离的结构,我们称之为桥接模式。

实现

  • 示例1:

Display.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.edu.tju.GOF.Bridge;


public class Display {
private DisplayImpl impl;
public Display(DisplayImpl impl) {
this.impl = impl;
}
public void open() {
impl.rawOpen();
}
public void print() {
impl.rawPrint();
}
public void close() {
impl.rawClose();
}
public final void display() {
open();
print();
close();
}
}

CountDisplayl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.edu.tju.GOF.Bridge;

public class CountDisplay extends Display {
public CountDisplay(DisplayImpl impl) {
super(impl);
}

public void multiDisplay(int times) { // 循环显示times次
open();
for (int i = 0; i < times; i++) {
print();
}
close();
}
}

DisplayImpl.java

1
2
3
4
5
6
7
package com.edu.tju.GOF.Bridge;

public abstract class DisplayImpl {
public abstract void rawOpen();
public abstract void rawPrint();
public abstract void rawClose();
}

l
StringDisplayImpl.java

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
package com.edu.tju.GOF.Bridge;


public class StringDisplayImpl extends DisplayImpl {
private String string; // 要显示的字符串
private int width; // 以字节单位计算出的字符串的宽度

public StringDisplayImpl(String string) { // 构造函数接收要显示的字符串string
this.string = string; // 将它保存在字段中
this.width = string.getBytes().length; // 把字符串的宽度也保存在字段中,以供使用。
}

public void rawOpen() {
printLine();
}

public void rawPrint() {
System.out.println("|" + string + "|"); // 前后加上"|"并显示
}

public void rawClose() {
printLine();
}

private void printLine() {
System.out.print("+"); // 显示用来表示方框的角的"+"
for (int i = 0; i < width; i++) { // 显示width个"-"
System.out.print("-"); // 将其用作方框的边框
}
System.out.println("+"); // 显示用来表示方框的角的"+"
}
}

Main.java

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.edu.tju.GOF.Bridge;

public class Main {
public static void main(String[] args) {
Display d1 = new Display(new StringDisplayImpl("Hello, China."));
Display d2 = new CountDisplay(new StringDisplayImpl("Hello, World."));
CountDisplay d3 = new CountDisplay(new StringDisplayImpl("Hello, Universe."));
d1.display();
d2.display();
d3.display();
d3.multiDisplay(5);
}
}

当然也可以实现用其他的形式”显示”,比如实现”显示字符串若干(随机)次”的功能.

写这段代码的时候,感觉和对象适配器模式实现很像.都用到了委托

  • 示例2:
    从晚上找到了这个例子,感觉很生动形象:
    设备Device类作为实现部分, 而 遥控器Remote类则作为抽象部分。

最初类层次结构被拆分为两部分,设备和遥控器

遥控器基类声明了一个指向设备对象的引用成员变量。 所有遥控器通过通用设备接口与设备进行交互, 使得同一个遥控器可以支持不同类型的设备。

同时可以开发独立于设备类的遥控器类, 只需新建一个遥控器子类即可。 例如, 基础遥控器可能只有两个按钮, 但你可在其基础上扩展新功能, 比如额外的一节电池或一块触摸屏。

客户端代码通过遥控器构造函数将特定种类的遥控器与设备对象连接起来。

这里只提供伪代码

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
// “抽象部分”定义了两个类层次结构中“控制”部分的接口。它管理着一个指向“实
// 现部分”层次结构中对象的引用,并会将所有真实工作委派给该对象。
class RemoteControl is
protected field device: Device
constructor RemoteControl(device: Device) is
this.device = device
method togglePower() is
if (device.isEnabled()) then
device.disable()
else
device.enable()
method volumeDown() is
device.setVolume(device.getVolume() - 10)
method volumeUp() is
device.setVolume(device.getVolume() + 10)
method channelDown() is
device.setChannel(device.getChannel() - 1)
method channelUp() is
device.setChannel(device.getChannel() + 1)


// 你可以独立于设备类的方式从抽象层中扩展类。
class AdvancedRemoteControl extends RemoteControl is
method mute() is
device.setVolume(0)


// “实现部分”接口声明了在所有具体实现类中通用的方法。它不需要与抽象接口相
// 匹配。实际上,这两个接口可以完全不一样。通常实现接口只提供原语操作,而
// 抽象接口则会基于这些操作定义较高层次的操作。
interface Device is
method isEnabled()
method enable()
method disable()
method getVolume()
method setVolume(percent)
method getChannel()
method setChannel(channel)


// 所有设备都遵循相同的接口。
class Tv implements Device is
// ...

class Radio implements Device is
// ...


// 客户端代码中的某个位置。
tv = new Tv()
remote = new RemoteControl(tv)
remote.togglePower()

radio = new Radio()
remote = new AdvancedRemoteControl(radio)

桥接模式 适合场景

  • 如果你想要拆分或重组一个具有多重功能的庞杂类 (例如能与多个数据库服务器进行交互的类), 可以使用桥接模式。
    类的代码行数越多, 弄清其运作方式就越困难, 对其进行修改所花费的时间就越长。 一个功能上的变化可能需要在整个类范围内进行修改, 而且常常会产生错误, 甚至还会有一些严重的副作用。

    桥接模式可以将庞杂类拆分为几个类层次结构。 此后, 你可以修改任意一个类层次结构而不会影响到其他类层次结构。 这种方法可以简化代码的维护工作, 并将修改已有代码的风险降到最低。

  • 如果你希望在几个独立维度上扩展一个类, 可使用该模式。
    桥接建议将每个维度抽取为独立的类层次。 初始类将相关工作委派给属于对应类层次的对象, 无需自己完成所有工作。

  • 如果你需要在运行时切换不同实现方法, 可使用桥接模式。

实现方式

  1. 明确类中独立的维度。 独立的概念可能是: 抽象/平台, 域/基础设施, 前端/后端或接口/实现。

  2. 了解客户端的业务需求, 并在抽象基类中定义它们。

  3. 确定在所有平台上都可执行的业务。 并在通用实现接口中声明抽象部分所需的业务。

  4. 为你域内的所有平台创建实现类, 但需确保它们遵循实现部分的接口。

  5. 在抽象类中添加指向实现类型的引用成员变量。 抽象部分会将大部分工作委派给该成员变量所指向的实现对象。

  6. 如果你的高层逻辑有多个变体, 则可通过扩展抽象基类为每个变体创建一个精确抽象。

  7. 客户端代码必须将实现对象传递给抽象部分的构造函数才能使其能够相互关联。 此后, 客户端只需与抽象对象进行交互, 无需和实现对象打交道。

优缺点

  • 桥接模式优点

    • 你可以创建与平台无关的类和程序。

    • 客户端代码仅与高层抽象部分进行互动, 不会接触到平台的详细信息。

    • 开闭原则。 你可以新增抽象部分和实现部分, 且它们之间不会相互影响。

    • 单一职责原则。 抽象部分专注于处理高层逻辑, 实现部分处理平台细节。

  • 桥接模式缺点

    • 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。 – 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性。

      Bridge 模式中的登场角色

  • Abstraction (抽象化)
    该角色位于”类的功能层次结构”的最上层,它使用了Implementor角色的方法定义了基本的功能.该角色中保存了Implementor角色的实例.
    Display扮演此角色

  • RefinedAbstraction(改善后的抽象化)
    在Abstraction的基础上增加了新功能的角色.
    由CountDisplay类扮演

  • Implementor(实现者)
    该角色位于”类的实现层次结构”的最上层.它定义了用于实现Abstraction角色的接口(API)的方法
    由 DisplayImpl类扮演此角色.

  • ConcreteImplementor(具体实现者)
    该角色负责实现Implementor角色中定义的接口(API).在实例程序中,由StringDisplayImpl类扮演此橘色

  • Bridge模式的类图
    类的两个层次结构之间的桥梁是impl字段

    拓展思路的要点

  • 分开后 更容易扩展
    Bridge模式将 类的功能层次结构类的实现层次结构 分离开了.将类的者两个层次结构分离开有利于独立地对它们进行扩展.
    当想要增加功能时,只需要在类的功能层次结构一侧增加类即可,不必对”类的实现层次结构”做任何修改.而且,增加后的共嗯那个可以被”所有的实现”使用.

  • 继承是强关联,委托是弱关联
    只有Display类的实例生成时,才与作为参数被传入的类构成关联.例如,在程序中,当Main类生成Display类和CountDisplay类的实例时,才将StringDisplayImpl的实例作为参数传递给Display类和CountDisplay类.
    如果我们不传递StringDisplayImpl类的实例,而是将其他ConcreteImplementor角色的实例传递给Display类和CountDisplay类,就能很容易地改变实现.这时,发生变化的代码只有Main类,Display类和DisplayImpl类则不需要做任何修改.

与其他模式的关系

  • 桥接模式通常会于开发前期进行设计, 使你能够将程序的各个部分独立开来以便开发。 另一方面, 适配器模式通常在已有程序中使用, 让相互不兼容的类能很好地合作。

  • 桥接、 状态模式策略模式 (在某种程度上包括适配器) 模式的接口非常相似。 实际上, 它们都基于组合模式——即将工作委派给其他对象, 不过也各自解决了不同的问题。 模式并不只是以特定方式组织代码的配方, 你还可以使用它们来和其他开发者讨论模式所解决的问题。

  • 你可以将抽象工厂模式和桥接搭配使用。 如果由桥接定义的抽象只能与特定实现合作, 这一模式搭配就非常有用。 在这种情况下, 抽象工厂可以对这些关系进行封装, 并且对客户端代码隐藏其复杂性。

Thank you for your support