C++ 设计模式笔记——3. 结构型模式
Adapter (适配器)
适配器模式是一种结构型设计模式,它使接口不兼容的对象能够相互合作。
假设我们已有一个现有类Adaptee
,它提供接口specificRequest()
,客户端希望调用接口类Target
的request()
方法,
两个接口无法直接相连,并且可能存在一些差异,例如函数参数顺序。
出于某些原因,我们无法更改旧有代码,那么可以选择在其中加上适配器Adapter
:
- 适配器
Adapter
直接继承接口类Target
,可以对客户端提供request()
方法; - 适配器
Adapter
将现有类Adaptee
作为数据成员,在request()
方法中实际调用Adaptee
对象的specificRequest()
,在传递过程中还需要处理一些差异,例如调整函数参数顺序。
示例代码如下 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
// 目标接口类
class Target {
public:
// 客户端希望使用的接口
virtual void request(int a, double b) = 0;
virtual ~Target() = default;
};
// 需要适配的类
class Adaptee {
public:
// 现有代码提供的接口
void specificRequest(double x, int n) {
std::cout << "Adaptee specific request with x = " << x
<< " and n = " << n << "." << std::endl;
}
};
// 适配器类
class Adapter : public Target {
private:
std::shared_ptr<Adaptee> m_adaptee;
public:
explicit Adapter(std::shared_ptr<Adaptee> adaptee)
: m_adaptee(std::move(adaptee)) {}
void request(int n, double x) override {
// 调用实际的接口,还要处理两者的差异,例如调整参数顺序
m_adaptee->specificRequest(x, n);
}
};
// 客户端代码
void clientCode(const std::shared_ptr<Target> &target, int a, double b) {
target->request(a, b);
}
int main() {
std::shared_ptr<Adaptee> adaptee = std::make_shared<Adaptee>();
std::shared_ptr<Target> adapter = std::make_shared<Adapter>(adaptee);
clientCode(adapter, 10, 20.5);
return 0;
}
除了将实际接口对应的类作为适配器的数据成员,还可以让适配器继承实际接口对应的类,此时适配器处于多继承状态, 这种做法通常被称为类适配器,与之相对的,示例代码中的做法被称为对象适配器。
Bridge (桥接)
桥接模式是一种结构型设计模式,可将一个大类或一系列紧密相关的类拆分为抽象和实现两个独立的层次结构,从而能在开发时分别使用。
考虑一种使用关系:图形需要拥有颜色属性,我们可能会支持很多种图形,支持很多种颜色, 如果为每一种颜色的每一种图形都创建对应的类型,实在是过于繁复了,桥接模式就是解决这类问题的,我们将划分两个部分:
- 图形基类
Shape
以及对应的派生类(Rectangle
,Circle
等)属于使用关系中的抽象部分; - 颜色基类
Color
以及对应的派生类(RedColor
,BlueColor
等)属于使用关系中的实现部分。
然后只需要维护图形基类对颜色基类的使用关系——图形拥有颜色,两者的派生类就会自动保持对应的使用关系。 这两个抽象基类搭建起了两部分之间的桥梁,因此名为桥接模式,它对于后续的功能扩展非常有利,尤其是两部分都需要进行扩展的情况。
示例代码如下 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
67
68
69
70
71
72
73
// 实现部分
class Color {
public:
virtual std::string fill() const = 0;
virtual ~Color() = default;
};
class RedColor : public Color {
public:
std::string fill() const override { return "Red"; }
};
class BlueColor : public Color {
public:
std::string fill() const override { return "Blue"; }
};
// 抽象部分
class Shape {
protected:
std::shared_ptr<Color> color;
public:
explicit Shape(std::shared_ptr<Color> color) : color(std::move(color)) {}
virtual void draw() const = 0;
virtual ~Shape() = default;
};
class Circle : public Shape {
private:
double m_radius;
public:
Circle(std::shared_ptr<Color> color, double radius)
: Shape(color), m_radius(radius) {}
void draw() const override {
std::cout << "Drawing Circle with radius " << m_radius << " and color "
<< color->fill() << std::endl;
}
};
class Rectangle : public Shape {
private:
double m_width;
double m_height;
public:
Rectangle(std::shared_ptr<Color> color, double width, double height)
: Shape(color), m_width(width), m_height(height) {}
void draw() const override {
std::cout << "Drawing Rectangle with width " << m_width << ", height "
<< m_height << " and color " << color->fill() << std::endl;
}
};
int main() {
auto red = std::make_shared<RedColor>();
Circle circle(red, 5.0);
circle.draw();
auto blue = std::make_shared<BlueColor>();
Rectangle rectangle(blue, 3.0, 4.0);
rectangle.draw();
return 0;
}
Composite (组合)
组合模式是一种结构型设计模式,可以使用它将对象组合成树状结构,并且能像使用独立对象一样使用它们,组合模式又称为对象树。
树作为一种底层数据结构,它的思想仍然可以在面向对象中使用,我们可以让独立对象组合树状结构,便于对大量对象的管理和使用。 我们既可以在叶子节点和普通节点上都存储对象,也可以让普通节点只起到容器的作用,只在叶子节点中存储对象。
为了形成树结构,我们至少需要提供一个节点基类Component
,并且派生出节点类型(叶子节点Leaf
和普通节点Composite
),
然后定义必要的接口,例如为普通节点添加子节点add
和删除子节点remove
。
在形成树结构之后,我们就可以很方便地使用,例如提供从根节点遍历所有节点的接口operation
。
示例代码如下 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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
// 抽象节点基类
class Component {
protected:
std::string name;
public:
explicit Component(std::string name) : name(std::move(name)) {}
virtual ~Component() = default;
virtual void operation(int indent) const = 0;
virtual void add(std::shared_ptr<Component> component) = 0;
virtual void remove(std::shared_ptr<Component> component) = 0;
protected:
static auto get_indent(int indent) { return std::string(indent, ' '); }
};
// 叶子节点
class Leaf : public Component {
public:
explicit Leaf(const std::string &name) : Component(name) {}
void operation(int indent) const override {
std::cout << get_indent(indent) << "call operation on Leaf " << name
<< std::endl;
}
void add(std::shared_ptr<Component> component) override {}
void remove(std::shared_ptr<Component> component) override {}
};
// 普通节点
class Composite : public Component {
private:
std::vector<std::shared_ptr<Component>> m_children;
public:
explicit Composite(const std::string &name) : Component(name) {}
void operation(int indent) const override {
std::cout << get_indent(indent) << "call operation on Composite "
<< name << std::endl;
for (const auto &child : m_children) { child->operation(indent + 4); }
}
void add(std::shared_ptr<Component> component) override {
m_children.push_back(component);
}
void remove(std::shared_ptr<Component> component) override {
m_children.erase(
std::remove(m_children.begin(), m_children.end(), component),
m_children.end());
}
};
int main() {
auto leaf1 = std::make_shared<Leaf>("1");
auto leaf2 = std::make_shared<Leaf>("2");
auto composite3 = std::make_shared<Composite>("3");
composite3->add(leaf1);
composite3->add(leaf2);
auto composite4 = std::make_shared<Composite>("4");
composite4->add(composite3);
auto leaf5 = std::make_shared<Leaf>("5");
composite4->add(leaf5);
composite4->operation(0);
return 0;
}
组合模式可以用来定义某些允许递归定义的类,例如Python原生列表可以存储不同类型的元素,元素也可以是原生列表。
Decorator (装饰)
装饰模式是一种结构型设计模式,它通过将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为。
首先创建组件的抽象基类ComponentBase
,它预留了接口call()
,然后:
- 继承
ComponentBase
以创建具体的组件类Component
,具体实现了call()
; - 继承
ComponentBase
以创建装饰器基类DecoratorBase
,它拥有一个指向ComponentBase
的指针m_component
,并且实现的call()
就是在调用m_component->call()
;(如果只需要一个装饰器的话,我们其实不需要实现装饰器基类,直接实现装饰器即可) - 继承
DecoratorBase
以创建具体的装饰器DecoratorA
和DecoratorB
,它们实现的call()
在调用m_component->call()
的前后加入了额外的行为,这是装饰器的核心。
示例代码如下 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
// 组件基类
class ComponentBase {
public:
virtual std::string call() const = 0;
virtual ~ComponentBase() = default;
};
// 具体组件
class Component : public ComponentBase {
public:
std::string call() const override { return "Component"; }
};
// 装饰器基类
class DecoratorBase : public ComponentBase {
protected:
std::shared_ptr<ComponentBase> m_component;
public:
explicit DecoratorBase(std::shared_ptr<ComponentBase> component)
: m_component(std::move(component)) {}
std::string call() const override { return m_component->call(); }
};
// 具体装饰器A
class DecoratorA : public DecoratorBase {
public:
explicit DecoratorA(std::shared_ptr<ComponentBase> component)
: DecoratorBase(component) {}
std::string call() const override {
return "DecoratorA (" + DecoratorBase::call() + ")";
}
};
// 具体装饰器B
class DecoratorB : public DecoratorBase {
public:
explicit DecoratorB(std::shared_ptr<ComponentBase> component)
: DecoratorBase(component) {}
std::string call() const override {
return "Decorator B(" + DecoratorBase::call() + ")";
}
};
int main() {
auto simple = std::make_shared<Component>();
std::cout << "Simple: " << simple->call() << std::endl;
auto decorated1 = std::make_shared<DecoratorA>(simple);
std::cout << "Decorated with A: " << decorated1->call() << std::endl;
auto decorated2 = std::make_shared<DecoratorB>(decorated1);
std::cout << "Decorated with A and B: " << decorated2->call() << std::endl;
return 0;
}
运行结果如下 1
2
3Simple: Component
Decorated with A: DecoratorA (Component)
Decorated with A and B: Decorator B(DecoratorA (Component))
这里我们使用DecoratorA
装饰器将原本的组件装饰之后,还可以继续用DecoratorB
装饰器进行装饰,即进行多层装饰。
值得一提的是,在Python中直接提供了装饰器的语法,可以对函数进行装饰,在函数执行前后加入特定的行为,例如计时和添加日志。Python中的装饰器的示例代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20def logger(func):
def wrapper(*args, **kwargs):
print('[begin {}]'.format(func.__name__))
func(*args, **kwargs)
print('[end {}]'.format(func.__name__))
return wrapper
def test(x):
print("hello, ",x)
test("Alex")
'''
[begin test]
hello, Alex
[end test]
'''
Facade (外观)
外观模式是一种结构型设计模式,可以为程序库、框架或其他复杂类提供一个简单的接口,隐藏内部的繁琐细节。
假设我们需要使用多个子系统:SubsystemA
,SubsystemB
和SubsystemC
,每一个系统都需要相应的配置,
这导致用户使用过于复杂,我们提供一个外观类Facade
将几个子系统打包,通过外观类的简单接口自动完成子系统的配置。
1 |
|
Flyweight (享元)
享元模式是一种结构型设计模式,它摒弃了在每个对象中保存所有数据的方式,通过共享多个对象所共有的相同状态,可以在有限的内存容量中载入更多对象,特别适用于需要产生大量相似对象的情景。
我们需要将对象的状态拆分为内部状态和外部状态,内部状态是对象之间可以共享的,外部状态是每个对象独有的。 享元模式通常结合工厂模式来使用:享元工厂需要创建大量相似的对象,工厂在内部维护了一个缓存池,在创建对象时会复用缓存池中已有的内部状态,从而节约内存。
享元类Flyweight
包含一个内部状态intrinsicState
,以及一个call()
方法,接受外部状态extrinsicState
作为参数。
享元工厂类FlyweightFactory
负责享元对象的创建和共享,它使用一个哈希表m_flyweights
存储享元对象,哈希表以内部状态为索引。
享元工厂对外提供getFlyweight()
方法获取享元对象,还提供getFlyweightCount()
方法获取实际创建的享元对象个数。
示例代码如下 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 Flyweight {
private:
char m_intrinsicState; // 内部状态
public:
explicit Flyweight(char state) : m_intrinsicState(state) {}
void call(const std::string &extrinsicState) const {
std::cout << "Intrinsic State: " << m_intrinsicState
<< ", Extrinsic State: " << extrinsicState << std::endl;
}
};
// 享元工厂类
class FlyweightFactory {
private:
// 哈希表,以内部状态为索引,在工厂内部缓存享元对象
std::unordered_map<char, std::shared_ptr<Flyweight>> m_flyweights;
public:
std::shared_ptr<Flyweight> getFlyweight(char state) {
if (m_flyweights.find(state) == m_flyweights.end()) {
m_flyweights[state] = std::make_shared<Flyweight>(state);
}
return m_flyweights[state];
}
size_t getFlyweightCount() const { return m_flyweights.size(); }
};
int main() {
FlyweightFactory factory;
auto flyweight1 = factory.getFlyweight('A');
flyweight1->call("First");
auto flyweight2 = factory.getFlyweight('B');
flyweight2->call("Second");
auto flyweight3 = factory.getFlyweight('A');
flyweight3->call("Third");
auto flyweight4 = factory.getFlyweight('A');
flyweight4->call("Fourth");
std::cout << "Total Flyweights created: " << factory.getFlyweightCount()
<< std::endl;
return 0;
}
Proxy (代理)
代理模式是一种结构型设计模式,让我们能够提供对象的替代品或其占位符。代理控制着对于原对象的访问,并允许在将请求提交给对象前后进行一些处理。
代理模式的典型应用情景如下:
延迟初始化(虚拟代理):如果有一个偶尔使用的重量级服务对象,一直保持该对象运行会大量消耗系统资源时,可以为其创建代理,无需在程序启动时就创建该对象,将对象的初始化延迟到真正有需要的时候。
访问控制(保护代理):如果希望只允许特定客户端使用服务对象,但是同时存在恶意程序也在尝试访问服务对象,此时可为服务对象创建代理,代理可仅在客户端凭据满足要求时,将请求传递给服务对象,从而阻止非法访问。
本地执行远程服务(远程代理):如果服务对象实际位于远程服务器上,可以为其创建代理,由代理负责处理所有与网络相关的复杂细节。
记录日志请求(日志记录代理):如果需要保存对于服务对象的请求历史记录时,可以为其创建代理,由代理负责在请求传递前后记录日志。
缓存请求结果(缓存代理):如果需要缓存客户端的请求结果并对缓存生命周期进行管理时,特别是当返回结果的体积非常大,或者请求非常耗时,可以为其创建代理,代理对重复请求所需的相同结果进行缓存,使用请求参数作为索引缓存的键值,如果缓存命中则直接返回结果,无需实际传递请求。
示例代码将尽可能模拟上面的各种情景,首先定义抽象的数据库基类AbstractDatabase
,提供数据库查询接口request(query)
。
然后分别继承数据库基类来定义:
RealDatabase
:代表真实的远程数据库,实现查询接口并返回对应的结果,为了模拟远程通讯,在查询过程中加入了延时;ProxyDatabase
:代表代理的本地数据库,实现的查询接口逻辑比较复杂:- 首先调用
check_access()
进行访问前检查 - 然后查询代理内部的缓存,如果缓存中存在结果则直接返回
- 如果缓存失败,则通过真实的远程数据库查询(远程数据库对象的创建直接延迟到第一个远程查询时)
- 将查询结果添加到缓存中
- 调用
log_access()
记录日志
- 首先调用
示例代码如下 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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
// 抽象的数据库基类
class AbstractDatabase {
public:
virtual std::string request(const std::string &query) const = 0;
virtual ~AbstractDatabase() = default;
};
// 真实的远程数据库
class RealDatabase : public AbstractDatabase {
public:
std::string request(const std::string &query) const override {
// 模拟远程数据库查询,具有延迟
std::cout << "[Real] Processing query: " << query << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
return "Result from remote database for " + query;
}
};
// 代理的本地数据库
class ProxyDatabase : public AbstractDatabase {
private:
mutable std::unique_ptr<RealDatabase> m_realDatabase;
mutable std::unordered_map<std::string, std::string> m_cache;
bool check_access() const {
// 访问前检查
std::cout << "[Proxy] Checking access prior to firing a real request."
<< std::endl;
return true;
}
void log_access(const std::string &query) const {
// 访问后记录日志
std::cout << "[Proxy] Logging the request for query: " << query
<< std::endl;
}
public:
std::string request(const std::string &query) const override {
// 如果无法访问,直接返回空值
if (!check_access()) return "";
// 检查缓存,如果存在则直接返回
auto it = m_cache.find(query);
if (it != m_cache.end()) {
std::cout << "[Proxy] Returning cached result for query: " << query
<< std::endl;
return it->second;
}
// 如果缓存中没有,实际执行查询并缓存结果
if (!m_realDatabase) {
// 第一次访问时创建
m_realDatabase = std::make_unique<RealDatabase>();
}
std::string result = m_realDatabase->request(query);
m_cache[query] = result;
// 访问后记录日志
log_access(query);
return result;
}
};
int main() {
std::unique_ptr<AbstractDatabase> subject =
std::make_unique<ProxyDatabase>();
std::vector<std::string> queries{"query1", "query2", "query1", "query1"};
for (const auto &query : queries) {
std::cout << subject->request(query) << std::endl;
}
return 0;
}
运行结果如下,只有前两次远程查询有明显延迟,最后两次查询直接使用了本地缓存。
1
2
3
4
5
6
7
8
9
10
11
12
13
14[Proxy] Checking access prior to firing a real request.
[Real] Processing query: query1
[Proxy] Logging the request for query: query1
Result from remote database for query1
[Proxy] Checking access prior to firing a real request.
[Real] Processing query: query2
[Proxy] Logging the request for query: query2
Result from remote database for query2
[Proxy] Checking access prior to firing a real request.
[Proxy] Returning cached result for query: query1
Result from remote database for query1
[Proxy] Checking access prior to firing a real request.
[Proxy] Returning cached result for query: query1
Result from remote database for query1