【设计模式】适配器模式和桥接模式

本文最后更新于:2023年12月17日 下午

Adapter(适配器)——类对象结构型模式

1. 意图

将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

2. 别名

包装器(wrapper)

3. 动机

  • 有时,为复用而设计的工具箱类不能够被复用的原因仅仅是因为它的接口与专业应用领域所需要的接口不匹配(名称不一样,参数不一样等等)[1]

  • 我们可以改变工具箱类使它兼容专业领域中的类的接口,但前提是必须有这个工具箱的源代码,然而 即使我们得到了这些源代码,修改工具箱也是没有什么意义的,因为不应该仅仅为了实现一个应用,工具箱就不得不采用一些与特定领域相关的接口。

  • 我们可以不用上面的方法,而定义一个适配器类,由他来适配工具箱接口和专业应用的接口。我们可以用两种方法做这件事:

    • 继承专业应用类的接口和工具箱类的实现。这种方法对应Adapter模式的类版本(多继承)
    • 将工具箱类的实现作为适配器类的组成部分,并且使用工具箱的接口实现适配器类。这种方法对应Adapter模式的对象版本。

4. 适用性

以下情况使用Adapter模式:

  • 如果你想使用一个已经存在的类,而它的接口不符合你的需求
  • 你想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作。
  • (仅适用于对象Adapter)你想使用一些已经存在的子类,但是不可能对每一个都进行子类化以匹配他们的接口。对象适配器可以适配它的父类接口。

5. 结构

类适配器使用多重继承对一个接口与另一个接口进行匹配,如下图所示。

类适配器示意图

对象匹配器依赖于对象组合,如下图所示。

对象适配器示意图

6. 参与者

  • Target
    • 定义Client使用的与特定领域相关的接口
  • Client
    • 与符合Target接口的对象协同
  • Adaptee
    • 定义一个已经存在的接口,与这个接口需要适配
  • Adapter
    • 对Adaptee的接口与Target接口进行适配

7. 协作

Client在Adapter实例上调用一些操作。接着适配器调用Adaptee的操作实现这个请求。

8. 效果

类适配器和对象适配器有不同的权衡。类适配的权衡为:

  • 用一个具体的Adapter类对Adaptee和Target进行匹配。结果是当我们想要匹配一个类以及所有它的子类时,类Adapter将不能胜任工作。
  • 使得Adapter可以重新定义Adaptee的部分行为,因为Adapter是Adaptee的一个子类。
  • 仅仅引入一个对象,并不需要额外的指针以间接得到Adaptee。

对象适配器的权衡为:

  • 允许一个Adater与多个Adaptee(即Adaptee本身以及它的所有子类(如果有子类的话)同时工作。Adapter也可以一次给所有的Adaptee添加功能。
  • 使得重定义Adaptee的行为比较困难。这就需要生成Adaptee的子类并且使得Adapter引用这个子类而不是引用Adaptee本身。

9. 示例代码

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
// Adapter.h
// Created by 张宇轩 on 2022/4/24.
// Copyright (c) 2022 zhangyuxuan. All rights reserved.
//
#include <iostream>

using namespace std;
#ifndef CREATIONPATTERNDESIGN_ADAPTER_H
#define CREATIONPATTERNDESIGN_ADAPTER_H

// 使用现实生活的接口转接器的例子,来展示使用Adapter模式
// mac 上只有 typec接口,而外设键盘是USB接口,通过转换器将 USB接口转换为 typec接口输出
void test_adapter();

// USB接口输出
class USB {
public:
void output_usb_data() {
cout << "USB接口 输出数据 ... " << endl;
}
};

// Typec 接口输出
class Typec {
public:
virtual void output_typec_data() {
cout << "Typec接口 输出数据 ... " << endl;
}
};

// 接口转接器,将USB输出转换为 Typec输出
class Adapter : public Typec {
public:
Adapter(USB *usb) : usb(usb) {}
virtual void output_typec_data() {
usb->output_usb_data();
// 进行转换处理工作
cout << "正在解析 usb 输出数据,进行转换..." << endl;

cout << "Typec接口 输出数据 ... " << endl;
}

private:
USB* usb;
};

class Mac {
public:
void input_typec(Typec * typec) {
if (typec != nullptr) {
interface = typec;
}
}
void work() {
cout << "Mac 开始工作..." << endl;
interface->output_typec_data();
}

private:
Typec *interface;
};


#endif //CREATIONPATTERNDESIGN_ADAPTER_H
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Adapter.cpp
// Created by 张宇轩 on 2022/4/24.
// Copyright (c) 2022 zhangyuxuan. All rights reserved.
//
#include <iostream>
#include "Adapter.h"
void test_adapter() {
cout << "——————————开始测试适配器模式——————————" << endl;
Mac mac;
USB keyboard;
Adapter adapter(&keyboard);
mac.input_typec(&adapter);
mac.work();
cout << "——————————结束测试适配器模式——————————" << endl;
}

10. 相关模式

模式Bridge的结果与对象适配器类似,但是Bridge模式的出发点不同:Bridge的目的是将接口部分和实现部分分离,从而可以对它们较为容易也相对独立地加以改变。而Adapter则意味着改变一个已有对象的接口。

Decorator模式增强了其他对象的功能而同时又不改变它的接口,因此Decorator对应用程序的透明性比适配器要好。结果是Decorator支持递归组合,而纯粹使用适配器是不可能实现这一点的。

模式Proxy在不改变它的接口的条件下,为另一个对象定义了一个代理。

Bridge(桥接)——对象结构模型

1. 意图

将抽象部分与它的实现部分分离,使它们可以独立地变化。

2. 别名

Handle/Body

3. 动机

当一个抽象可能有多个实现时,通常用继承来协调它们。抽象类定义对该抽象的接口,而具体的子类则用不同方式加以实现。但是此方法有时不够灵活。继承机制将抽象部分与它的实现部分固定在一起,使得难以对抽象部分和实现部分独立的进行修改、扩充和重用。桥接模式把依赖具体实现,提升为依赖抽象,来完成对象和变化因素之间的低耦合,提高系统的可维护性和扩展性。桥接模式的主要目的是将一个对象的变化与其它变化隔离开,让彼此之间的耦合度最低。[2]

4. 适用性

以下情况下使用Bridge模式:

  • 你不希望在抽象和它的实现部分之间有一个固定的绑定关系。例如在程序运行时刻实现部分应可以被选择或者切换。
  • 类的抽象以及它的实现都应该可以通过生成子类方法加以扩充。这时的桥接模式使你可以对不同的抽象接口和实现部分进行组合,并分别对它们进行扩充。
  • 对一个抽象的实现部分的修改应对客户不产生影响,即客户的代码不必重新编译。
  • 想对客户完全隐藏抽象的实现部分。
  • 有许多类要生成,类的层次结构说明你必须得将一个对象分解成两个部分。
  • 你想在多个对象间共享实现(可能使用引用计数),但同时要求客户并不知道这一点。

5. 结构

结构图

6. 参与者

  • Abstraction
    • 定义抽象类的接口
    • 维护一个指向Implementor类型对象的指针
  • RefinedAbstraction
    • 扩充由Abstraction定义的接口
  • Implementor
    • 定义实现类的接口,该接口不一定要与Abstraction的接口完全一致,事实上这两个接口可以完全不同。一般来讲,Implementor接口仅提供基本操作,而Abstraction则定义了基于这些基本操作的较高层次的操作
  • ConcreteImplementor
    • 实现Implementor接口并定义它的具体实现

7. 协作

Abstraction 将 client 的请求转发给它的 Implementor对象。

8. 效果

Bridge模式有以下有点:

  • 分离接口及其实现部分

    。一个实现未必不变地绑定在一个接口上。抽象类的实现可以在运行时进行配置,一个对象甚至可以在运行时改变它的实现。

    • 将Abstraction与Implementor分离有助于降低对实现部分编译时的依赖,当改变一个实现类时,并不需要重新编译Abstraction类和它的客户程序。为了保证一个类库的不同版本之间的二进制兼容性,一定要有这个性质。
    • 另外,接口与实现分离有助于分层,从而产生更好的结构化系统,系统的高层部分仅需知道Abstraction和Implementor。
  • 提高可扩充性。你可以独立地对Abstraction和Implementor层次结构进行扩充。

  • 实现细节对客户透明。你可以对客户隐藏实现细节,例如共享Implementor对象以及相应的引用计数机制(如果有的话)。

9. 示例代码

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
// Bridge.h
// Created by 张宇轩 on 2022/4/24.
// Copyright (c) 2022 zhangyuxuan. All rights reserved.
//
#include <iostream>

#ifndef CREATIONPATTERNDESIGN_BRIDGE_H
#define CREATIONPATTERNDESIGN_BRIDGE_H

// 用图形 和 颜色 这一经典例子来演示 Bridge模式
void test_bridge();

using namespace std;
class Color {
public:
virtual void fill_color() = 0;
};

class Shape {
public:
Shape(Color* color) : color(color) {}
virtual void draw_shape() = 0;

protected:
Color* color;
};

class Rectangle : public Shape {
public:

Rectangle(Color* color) : Shape(color) {}
void draw_shape() {
color->fill_color();
cout << "绘制一个正方形" << endl;
}
};
class Circle : public Shape {
public:
Circle(Color* color) : Shape(color) {}
void draw_shape() {
color->fill_color();
cout << "绘制一个圆形" << endl;
}
};

class RedColor : public Color {
public:
void fill_color() {
cout << "填充背景颜色:红色" << endl;
}
};
class BlueColor : public Color {
public:
void fill_color() {
cout << "填充背景颜色:蓝色" << endl;
}
};

#endif //CREATIONPATTERNDESIGN_BRIDGE_H
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Bridge.cpp
// Created by 张宇轩 on 2022/4/24.
// Copyright (c) 2022 zhangyuxuan. All rights reserved.
//
#include <iostream>
#include "Bridge.h"
void test_bridge() {
cout << "——————————开始测试桥接模式——————————" << endl;
Color * red = new RedColor;
Color * blue = new BlueColor;
Shape* red_rectangle = new Rectangle(red);
red_rectangle->draw_shape();
Shape* blue_circle = new Rectangle(blue);
blue_circle->draw_shape();
cout << "——————————结束测试桥接模式——————————" << endl;
}

10. 相关模式

Abstract Factory 模式可以用来创建和配置一个特定的Bridge模式。

Adapter 模式用来帮助无关的类协同工作,它通常在系统设计完成后才会被使用。然而,Bridge模式则是在系统开始时就被使用,它使得抽象接口和实现部分可以独立进行改变。

备注/参考


【设计模式】适配器模式和桥接模式
https://2017zhangyuxuan.github.io/2022/04/24/2022-04/2022-04-24【设计模式】适配器模式和桥接模式/
作者
Zhang Yuxuan
发布于
2022年4月24日
许可协议