“单一职责”模式:在软件组件的设计中,如果责任划分的不清晰,使用继承得到的结果往往是随着需求的变化,子类急剧膨胀,同时充斥着重复代码,这时候的关键是划清责任

Decorator

Decorator 也被称为 Kit

动机

  • 在某些情况下我们可能会“过度地使用继承来扩展对象的功能”,由于继承为类型引入的静态特质,使得这种扩展方式缺乏灵活性;并且随着子类的增多(扩展功能的增多),各种子类的组合(扩展功能的组合)会导致更多子类的膨胀
  • 如何使“对象功能的扩展”能根据需要来动态地实现?同时避免“扩展功能的增多”带来子类膨胀问题?从而使得任何“功能扩展变化”所导致的影响力将为最低?

定义

Attach(组合) additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing(继承) for extending functionality(消除重复代码 & 减少子类个数).

实现

Naive 编译时装载

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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
// 业务操作
class Stream {
public:
virtual char Read(int number) = 0;
virtual void Seek(int position) = 0;
virtual void Write(char data) = 0;

virtual ~Stream() {}
};

// 主体类
class FileStream : public Stream {
public:
virtual char Read(int number) {
// 读文件流
}
virtual void Seek(int position) {
// 定位文件流
}
virtual void Write(char data) {
// 写文件流
}
};

class NetworkStream : public Stream {
public:
virtual char Read(int number) {
// 读网络流
}
virtual void Seek(int position) {
// 定位网络流
}
virtual void Write(char data) {
// 写网络流
}
};

class MemoryStream : public Stream {
public:
virtual char Read(int number) {
// 读内存流
}
virtual void Seek(int position) {
// 定位内存流
}
virtual void Write(char data) {
// 写内存流
}
};

// 扩展操作
class CryptoFileStream : public FileStream {
public:
virtual char Read(int number) {
// 额外的加密操作...
FileStream::Read(number); // 读文件流
}
virtual void Seek(int position) {
// 额外的加密操作...
FileStream::Seek(position); // 定位文件流
// 额外的加密操作...
}
virtual void Write(byte data) {
// 额外的加密操作...
FileStream::Write(data); // 写文件流
// 额外的加密操作...
}
};

class CryptoNetworkStream : : public NetworkStream {
public:
virtual char Read(int number) {
// 额外的加密操作...
NetworkStream::Read(number); // 读网络流
}
virtual void Seek(int position) {
// 额外的加密操作...
NetworkStream::Seek(position); // 定位网络流
// 额外的加密操作...
}
virtual void Write(byte data) {
// 额外的加密操作...
NetworkStream::Write(data); // 写网络流
// 额外的加密操作...
}
};

class CryptoMemoryStream : public MemoryStream {
public:
virtual char Read(int number) {
// 额外的加密操作...
MemoryStream::Read(number); // 读内存流
}
virtual void Seek(int position) {
// 额外的加密操作...
MemoryStream::Seek(position); // 定位内存流
// 额外的加密操作...
}
virtual void Write(byte data) {
// 额外的加密操作...
MemoryStream::Write(data); // 写内存流
// 额外的加密操作...
}
};

class BufferedFileStream : public FileStream {
//...
};

class BufferedNetworkStream : public NetworkStream {
//...
};

class BufferedMemoryStream : public MemoryStream {
//...
};

class CryptoBufferedFileStream : public FileStream {
public:
virtual char Read(int number) {
// 额外的加密操作...
// 额外的缓冲操作...
FileStream::Read(number); // 读文件流
}
virtual void Seek(int position) {
// 额外的加密操作...
// 额外的缓冲操作...
FileStream::Seek(position); // 定位文件流
// 额外的加密操作...
// 额外的缓冲操作...
}
virtual void Write(byte data) {
// 额外的加密操作...
// 额外的缓冲操作...
FileStream::Write(data); // 写文件流
// 额外的加密操作...
// 额外的缓冲操作...
}
};

void Process() {
// 编译时装配
CryptoFileStream *fs1 = new CryptoFileStream();

BufferedFileStream *fs2 = new BufferedFileStream();

CryptoBufferedFileStream *fs3 = new CryptoBufferedFileStream();
}

image-20240327135318203

这里通过继承添加功能的方式“编译时装配”,可以观察到:

  • 这样创建出来的类数量非常多,达到了1+n+nm!21+n+n \cdot \frac{m!}{2}
    • nn 是第二层流的种类数,mm 是扩展的功能数。
  • 第3层各个类中都有 FileStream::Read(number) 等重复语句

仔细考虑它们之间的关系:第1层到第2层是主体操作,是继承关系;第2层到第3层是扩展操作,不是继承关系。

根据面向对象设计原则(6):优先使用对象组合,而不是类继承

Good but not perfect 运行时装配

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
86
87
88
89
90
// 业务操作
class Stream {
public:
virtual char Read(int number) = 0;
virtual void Seek(int position) = 0;
virtual void Write(char data) = 0;

virtual ~Stream() {}
};

// 主体类
class FileStream : public Stream {
public:
virtual char Read(int number) {
// 读文件流
}
virtual void Seek(int position) {
// 定位文件流
}
virtual void Write(char data) {
// 写文件流
}
};

class NetworkStream : public Stream {
public:
virtual char Read(int number) {
// 读网络流
}
virtual void Seek(int position) {
// 定位网络流
}
virtual void Write(char data) {
// 写网络流
}
};

class MemoryStream : public Stream {
public:
virtual char Read(int number) {
// 读内存流
}
virtual void Seek(int position) {
// 定位内存流
}
virtual void Write(char data) {
// 写内存流
}
};

// 扩展操作
class CryptoStream : public Stream { // 这里继承是为了后面virtual函数接口规范
Stream* stream; //... = new CryptoStream();

public:
CryptoStream(Stream* stm) : stream(stm) {}

virtual char Read(int number) {
// 额外的加密操作...
stream->Read(number); // 读文件流
}
virtual void Seek(int position) {
// 额外的加密操作...
stream::Seek(position); // 定位文件流
// 额外的加密操作...
}
virtual void Write(byte data) {
// 额外的加密操作...
stream::Write(data); // 写文件流
// 额外的加密操作...
}
};

class BufferedStream : public Stream {
Stream* stream; //...

public:
BufferedStream(Stream* stm) : stream(stm) {}
//...
};

void Process() {
// 运行时装配
FileStream* s1 = new FileStream();
CryptoStream* s2 = new CryptoStream(s1);

BufferedStream* s3 = new BufferedStream(s1);

BufferedStream* s4 = new BufferedStream(s2);
}

编译时没有 CryptoFileStreamBufferedFileStream 这样的类,但是可以运行时通过组合的方式把它们装配起来,符合我们的要求。

  1. 应当优先使用对象组合,那么就在 CryptoStreamBufferedStream 中添加 *Stream
    根据《重构》:当一个变量都声明为某个类型子类的类型,那么把变量都声明为这个类型

    故在 CryptoStreamBufferedStream 中加入的应该是基类 Stream

  2. CryptoStreamBufferedStream 这两个扩展类既包含了 Stream 字段,又继承了 Stream 方法,这是为了后面virtual函数接口规范,能正常调用。

  3. 通过组合获得 CryptoBufferedFStream 效果。注意 BufferedStream* s4 = new BufferedStream(s2);CryptoStream* s2 = new CryptoStream(s1);,那么 s4 调用 Read() 时会先调用 BufferedStream::Read() 这会先进行额外的缓存操作,然后在此函数中调用 stream->Read(); 由于 stream 类型为 CryptoStream,所以又会调用加密版本的 Read()

Decorator 添加中间层DecoratorStream

根据《重构》:如果某个类,它的多个子类都有同样的字段,应该往上提。

  • 但是不能提到 Stream 类中,因为 FileStream 不需要这个字段。
  • 那么就应该设置一个中间类 DecoratorStream
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
86
87
88
89
90
91
92
93
94
95
96
// 业务操作
class Stream {
public:
virtual char Read(int number) = 0;
virtual void Seek(int position) = 0;
virtual void Write(char data) = 0;

virtual ~Stream() {}
};

// 主体类
class FileStream : public Stream {
public:
virtual char Read(int number) {
// 读文件流
}
virtual void Seek(int position) {
// 定位文件流
}
virtual void Write(char data) {
// 写文件流
}
};

class NetworkStream : public Stream {
public:
virtual char Read(int number) {
// 读网络流
}
virtual void Seek(int position) {
// 定位网络流
}
virtual void Write(char data) {
// 写网络流
}
};

class MemoryStream : public Stream {
public:
virtual char Read(int number) {
// 读内存流
}
virtual void Seek(int position) {
// 定位内存流
}
virtual void Write(char data) {
// 写内存流
}
};

// 扩展操作
class DecoratorStream : public Stream {
protected:
Stream* stream; //...

DecoratorStream(Stream* stm) : stream(stm) {}
};

class CryptoStream : public DecoratorStream {
public:
CryptoStream(Stream* stm) : DecoratorStream(stm) {}

virtual char Read(int number) {
// 额外的加密操作...
stream->Read(number); // 读文件流
}
virtual void Seek(int position) {
// 额外的加密操作...
stream::Seek(position); // 定位文件流
// 额外的加密操作...
}
virtual void Write(byte data) {
// 额外的加密操作...
stream::Write(data); // 写文件流
// 额外的加密操作...
}
};

class BufferedStream : public DecoratorStream {
Stream* stream; //...

public:
BufferedStream(Stream* stm) : DecoratorStream(stm) {}
//...
};

void Process() {
// 运行时装配
FileStream* s1 = new FileStream();

CryptoStream* s2 = new CryptoStream(s1);

BufferedStream* s3 = new BufferedStream(s1);

BufferedStream* s4 = new BufferedStream(s2);
}

类的种类数降到了 1+n+1+m1+n+1+m

结构

image-20240327140045498

  • Component 对应 Stream
  • ConcreteComponent 对应 FileStream, MemoryStream, NetwordStream
  • Decorator 对应 DecoratorStream
  • ConcreteDecorator 对应 CryptoStream, BufferedStream

总结

  1. 通过采用组合而非继承的手法,Decorator模式实现了在运行时动态扩展对象功能的能力,并且可以根据需要扩展多个功能。避免了使用继承带来的“灵活性差”和“多子类衍生问题”。

  2. Decorator 类在接口上表现为 is-a Component 的继承关系,记Decorator类继承了 Component 类所具有的接口。但在实现上又表现为 has-a Component 的组合关系,即 Decorator 类又失恋了另一个 Component 类。

    如果看到一个类父类是某类,里面又有那个类字段, 那么就可以高度怀疑是 Decorator 模式。

    有时可能看不到内部字段,但可以根据类的外部接口来判断:父类是某类,构造器参数也是那个类。

  3. Decorator 模式的目的并非解决“多子类衍生的多继承”问题,Decorator模式应用的要点在于解决“主体类在多个方向上的扩展功能”——是为了“装饰”的含义。

Bridge

Bridge 也被称为 Handle / Body

动机

  • 由于某些类型的固有实现逻辑,使得它们有两个变化的维度,乃至多个维度的变化。
  • 如何应对这种“多维度的变化”?如何利用面向对象技术来使得类型可以轻松地沿着两个乃至多个方向变化,而不引入额外的复杂度?

定义

Decouple an abstraction(业务功能) from its implementation(平台实现) so that the two can vary independently.

实现

Naive 编译时装配

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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
class Messager {
public:
virtual void Login(string username, string password) = 0;
virtual void SendMessage(string message) = 0;
virtual void SendPicture(Image image) = 0;

virtual void PlaySound() = 0;
virtual void DrawShape() = 0;
virtual void WriteText() = 0;
virtual void Connect() = 0;

virtual ~Messager() {}
};

// 平台实现
class PCMessagerBase : public Messager {
public:
virtual void PlaySound() {
//**********
}
virtual void DrawShape() {
//**********
}
virtual void WriteText() {
//**********
}
virtual void Connect() {
//**********
}
};

class MobileMessagerBase : public Messager {
public:
virtual void PlaySound() {
//==========
}
virtual void DrawShape() {
//==========
}
virtual void WriteText() {
//==========
}
virtual void Connect() {
//==========
}
};

// 业务抽象
class PCMessagerLite : public PCMessagerBase {
public:
virtual void Login(string username, string password) {
PCMessagerBase::Connect();
//........
}
virtual void SendMessage(string message) {
PCMessagerBase::WriteText();
//........
}
virtual void SendPicture(Image image) {
PCMessagerBase::DrawShape();
//........
}
};

class PCMessagerPerfect : public PCMessagerBase {
public:
virtual void Login(string username, string password) {
PCMessagerBase::PlaySound();
//********
PCMessagerBase::Connect();
//........
}
virtual void SendMessage(string message) {
PCMessagerBase::PlaySound();
//********
PCMessagerBase::WriteText();
//........
}
virtual void SendPicture(Image image) {
PCMessagerBase::PlaySound();
//********
PCMessagerBase::DrawShape();
//........
}
};

class MobileMessagerLite : public MobileMessagerBase {
public:
virtual void Login(string username, string password) {
MobileMessagerBase::Connect();
//........
}
virtual void SendMessage(string message) {
MobileMessagerBase::WriteText();
//........
}
virtual void SendPicture(Image image) {
MobileMessagerBase::DrawShape();
//........
}
};

class MobileMessagerPerfect : public MobileMessagerBase {
public:
virtual void Login(string username, string password) {
MobileMessagerBase::PlaySound();
//********
MobileMessagerBase::Connect();
//........
}
virtual void SendMessage(string message) {
MobileMessagerBase::PlaySound();
//********
MobileMessagerBase::WriteText();
//........
}
virtual void SendPicture(Image image) {
MobileMessagerBase::PlaySound();
//********
MobileMessagerBase::DrawShape();
//........
}
};

void Process() {
// 编译时装配
Messager *m = new MobileMessagerPerfect();
}

这里是**“编译时装配”**。

观察到子类有相似的结构和相同的虚函数名,识别到这种模式后,就可以采用和 Decorator 相同的处理方法:改继承为组合,包含基类成变量。

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
class Messager {
public:
virtual void Login(string username, string password) = 0;
virtual void SendMessage(string message) = 0;
virtual void SendPicture(Image image) = 0;

virtual void PlaySound() = 0;
virtual void DrawShape() = 0;
virtual void WriteText() = 0;
virtual void Connect() = 0;

virtual ~Messager() {}
};

// 平台实现
class PCMessagerBase : public Messager {
public:
virtual void PlaySound() {
//**********
}
virtual void DrawShape() {
//**********
}
virtual void WriteText() {
//**********
}
virtual void Connect() {
//**********
}
};

class MessagerLite {
Messager *messager; // new PCMessageBase()
public:
virtual void Login(string username, string password) {
messager->Connect();
//........
}
virtual void SendMessage(string message) {
messager->WriteText();
//........
}
virtual void SendPicture(Image image) {
messager->DrawShape();
//........
}
};

但这里有一个问题:
PCMessageBase 只override了实现相关的纯虚函数,没有override业务抽象相关的纯虚函数,其仍然是一个抽象类,无法实例化为一个成员变量。

Bridge 运行时装配

应该将抽象部分和实现部分拆分开。

抽象部分(业务功能)应该包含 MessagerImp (基类)指针,由于所有抽象业务类都应该有这样指针,所以一定把 MessagerImp 指针放到 Message 基类中。

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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
class Messager {
protected:
MessagerImp* messagerImp; //...
public:
virtual void Login(string username, string password) = 0;
virtual void SendMessage(string message) = 0;
virtual void SendPicture(Image image) = 0;

virtual ~Messager() {}
};

class MessagerImp {
public:
virtual void PlaySound() = 0;
virtual void DrawShape() = 0;
virtual void WriteText() = 0;
virtual void Connect() = 0;

virtual void MessagerImp() {}
};

// 平台实现 n
class PCMessagerImp : public MessagerImp {
public:
virtual void PlaySound() {
//**********
}
virtual void DrawShape() {
//**********
}
virtual void WriteText() {
//**********
}
virtual void Connect() {
//**********
}
};

class MobileMessagerImp : public MessagerImp {
public:
virtual void PlaySound() {
//==========
}
virtual void DrawShape() {
//==========
}
virtual void WriteText() {
//==========
}
virtual void Connect() {
//==========
}
};

// 业务抽象 m
// 类的数目:1+n+m
class MessagerLite : public Messager {
public:
virtual void Login(string username, string password) {
messagerImp->Connect();
//........
}
virtual void SendMessage(string message) {
messagerImp->WriteText();
//........
}
virtual void SendPicture(Image image) {
messagerImp->DrawShape();
//........
}
};

class MessagerPerfect : public Messager {
public:
virtual void Login(string username, string password) {
messagerImp->PlaySound();
//********
messagerImp->Connect();
//........
}
virtual void SendMessage(string message) {
messagerImp->PlaySound();
//********
messagerImp->WriteText();
//........
}
virtual void SendPicture(Image image) {
messagerImp->PlaySound();
//********
messagerImp->DrawShape();
//........
}
};

void Process() {
// 运行时装配
MessagerImp* mImp = new PCMessagerImp();
Messager* m = new Messager(mImp);
}

这里省略了构造函数。

结构

image-20240328090640083

  • Abstraction 相当于 Messager,内含 MessagerImp 指针
  • Implementor 相当于 MessagerImp
  • RefinedAbstraction 相当于 Lite, Perfect 版本
  • ConcreteImplementor 相当于 PC, Mobile 版本

总结

  1. Bridge 模式使用“对象间的组合关系(使用实现基类部分的指针)”解耦了抽象和实现之间的固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化。所谓抽象和实现沿着各自的维度变化,即“子类化”他们。
  2. Bridge模式有时候类似多继承方案,但是多继承方案往往违背单一职责原则(即一个类只有一个变化的原因),复用性比较差,Bridge模式是比多继承方案更好的解决方法。
  3. Bridge模式的应用一般在“两个非常强的变化维度”,有时一个类也有多于两个的变化维度,这时也可以使用 Bridge 扩展模式。