4. “单一职责”类模式
在软件组件的设计中,如果责任划分的不清晰,使用继承得到的结果往往是随着需求的变化,子类急剧膨胀,同时充斥着重复代码,这时候的关键是划清责任。
典型模式代表: Decorator,Bridge
4.1 Decorator 装饰模式
代码示例:不同的流操作(文件流,网络流,内存流)及其扩展功能(加密,缓冲)等的实现
实现代码1:
类图结构示意(大量使用继承)
数据规模: 假设有n种文件,m种功能操作。该实现方法有(1 + n + n * m! / 2) 数量级的子类;
同时考察59行,79行,98行本身是相同的代码(类似还有很多),存在大量的冗余和重复。
开始重构,见方法2.
1 //Decorator1.cpp
2 //业务操作
3 class Stream{
4 public:
5 virtual char Read(int number)=0;
6 virtual void Seek(int position)=0;
7 virtual void Write(char data)=0;
8
9 virtual ~Stream(){}
10 };
11
12 //主体类
13 class FileStream: public Stream{
14 public:
15 virtual char Read(int number){
16 //读文件流
17 }
18 virtual void Seek(int position){
19 //定位文件流
20 }
21 virtual void Write(char data){
22 //写文件流
23 }
24
25 };
26
27 class NetworkStream :public Stream{
28 public:
29 virtual char Read(int number){
30 //读网络流
31 }
32 virtual void Seek(int position){
33 //定位网络流
34 }
35 virtual void Write(char data){
36 //写网络流
37 }
38
39 };
40
41 class MemoryStream :public Stream{
42 public:
43 virtual char Read(int number){
44 //读内存流
45 }
46 virtual void Seek(int position){
47 //定位内存流
48 }
49 virtual void Write(char data){
50 //写内存流
51 }
52
53 };
54
55 //扩展操作
56 class CryptoFileStream :public FileStream{
57 public:
58 virtual char Read(int number){
59
60 //额外的加密操作...
61 FileStream::Read(number);//读文件流
62
63 }
64 virtual void Seek(int position){
65 //额外的加密操作...
66 FileStream::Seek(position);//定位文件流
67 //额外的加密操作...
68 }
69 virtual void Write(byte data){
70 //额外的加密操作...
71 FileStream::Write(data);//写文件流
72 //额外的加密操作...
73 }
74 };
75
76 class CryptoNetworkStream : :public NetworkStream{
77 public:
78 virtual char Read(int number){
79
80 //额外的加密操作...
81 NetworkStream::Read(number);//读网络流
82 }
83 virtual void Seek(int position){
84 //额外的加密操作...
85 NetworkStream::Seek(position);//定位网络流
86 //额外的加密操作...
87 }
88 virtual void Write(byte data){
89 //额外的加密操作...
90 NetworkStream::Write(data);//写网络流
91 //额外的加密操作...
92 }
93 };
94
95 class CryptoMemoryStream : public MemoryStream{
96 public:
97 virtual char Read(int number){
98
99 //额外的加密操作...
100 MemoryStream::Read(number);//读内存流
101 }
102 virtual void Seek(int position){
103 //额外的加密操作...
104 MemoryStream::Seek(position);//定位内存流
105 //额外的加密操作...
106 }
107 virtual void Write(byte data){
108 //额外的加密操作...
109 MemoryStream::Write(data);//写内存流
110 //额外的加密操作...
111 }
112 };
113
114 class BufferedFileStream : public FileStream{
115 //...
116 };
117
118 class BufferedNetworkStream : public NetworkStream{
119 //...
120 };
121
122 class BufferedMemoryStream : public MemoryStream{
123 //...
124 }
125
126
127
128
129 class CryptoBufferedFileStream :public FileStream{
130 public:
131 virtual char Read(int number){
132
133 //额外的加密操作...
134 //额外的缓冲操作...
135 FileStream::Read(number);//读文件流
136 }
137 virtual void Seek(int position){
138 //额外的加密操作...
139 //额外的缓冲操作...
140 FileStream::Seek(position);//定位文件流
141 //额外的加密操作...
142 //额外的缓冲操作...
143 }
144 virtual void Write(byte data){
145 //额外的加密操作...
146 //额外的缓冲操作...
147 FileStream::Write(data);//写文件流
148 //额外的加密操作...
149 //额外的缓冲操作...
150 }
151 };
152
153
154
155 void Process(){
156
157 //编译时装配
158 CryptoFileStream *fs1 = new CryptoFileStream();
159
160 BufferedFileStream *fs2 = new BufferedFileStream();
161
162 CryptoBufferedFileStream *fs3 =new CryptoBufferedFileStream();
163
164 }
实现代码2:
针对上述代码,重构步骤如下:
1)考察 CryptoFileStream ,CryptoNetworkStream,CryptoMemoryStream三个类,将其继承FileStream,NetworkStream,NetworkStream改为组合;即
1 class CryptoFileStream{
2 FileStream* stream;
3 public:
4 virtual char Read(int number){
5
6 //额外的加密操作...
7 stream -> Read(number);//改用字段方式调用Read()
8 // ...seek() write() 同理
9 }
10 }
11
12 class CryptoNetworkStream{
13 NetworkStream* stream;
14 public:
15 virtual char Read(int number){
16
17 //额外的加密操作...
18 stream -> Read(number);//改用字段方式调用Read()
19 //... seek() write() 同理
20 }
21 }
22
23 class CryptoMemoryStream{
24 MemoryStream* stream;
25 public:
26 virtual char Read(int number){
27
28 //额外的加密操作...
29 stream -> Read(number);//改用字段方式调用Read()
30 //... seek() write() 同理
31 }
32 }
2)考察上述2行, 13行, 24行, 发现其均为Stream子类, 应使用多态性继续重构。
1 class CryptoFileStream{
2 Stream* stream; // = new FileStream()
3 public:
4 virtual char Read(int number){
5
6 //额外的加密操作...
7 stream -> Read(number);//改用字段方式调用Read()
8 // ...seek() write() 同理
9 }
10 }
11
12 class CryptoNetworkStream{
13 Stream* stream; // = new NetworkStream();
14 public:
15 virtual char Read(int number){
16
17 //额外的加密操作...
18 stream -> Read(number);//改用字段方式调用Read()
19 //... seek() write() 同理
20 }
21 }
22
23 class CryptoMemoryStream{
24 Stream* stream; // = newMemoryStream()
25 public:
26 virtual char Read(int number){
27
28 //额外的加密操作...
29 stream -> Read(number);//改用字段方式调用Read()
30 //... seek() write() 同理
31 }
32 }
3)发现三个类是相同的,不同的实现(需求的变化)是在运行时实现,编译时复用,改为一个类即可,命名为CryptoStream。
同时为了保证接口规范(read,seek等仍然是虚函数),继承Stream,出现既有组合,又有继承的情况。
1 class CryptoStream : public Stream{
2 Stream* stream; // = new ...
3 public:
4 virtual char Read(int number){
5
6 //额外的加密操作...
7 stream -> Read(number);//改用字段方式调用Read()
8 // ...seek() write() 同理
9 }
10 }
4)添加相应构造器,得到此轮重构后的结果,代码如下,主要查看使用方式(运行时装配):
1 //Decorator2.cpp
2 class Stream{
3
4 public:
5 virtual char Read(int number)=0;
6 virtual void Seek(int position)=0;
7 virtual void Write(char data)=0;
8
9 virtual ~Stream(){}
10 };
11
12 //主体类
13 class FileStream: public Stream{
14 public:
15 virtual char Read(int number){
16 //读文件流
17 }
18 virtual void Seek(int position){
19 //定位文件流
20 }
21 virtual void Write(char data){
22 //写文件流
23 }
24
25 };
26
27 class NetworkStream :public Stream{
28 public:
29 virtual char Read(int number){
30 //读网络流
31 }
32 virtual void Seek(int position){
33 //定位网络流
34 }
35 virtual void Write(char data){
36 //写网络流
37 }
38
39 };
40
41 class MemoryStream :public Stream{
42 public:
43 virtual char Read(int number){
44 //读内存流
45 }
46 virtual void Seek(int position){
47 //定位内存流
48 }
49 virtual void Write(char data){
50 //写内存流
51 }
52
53 };
54
55 //扩展操作
56
57
58 class CryptoStream: public Stream {
59
60 Stream* stream;//...
61
62 public:
63 CryptoStream(Stream* stm):stream(stm){
64
65 }
66
67
68 virtual char Read(int number){
69
70 //额外的加密操作...
71 stream->Read(number);//读文件流
72 }
73 virtual void Seek(int position){
74 //额外的加密操作...
75 stream::Seek(position);//定位文件流
76 //额外的加密操作...
77 }
78 virtual void Write(byte data){
79 //额外的加密操作...
80 stream::Write(data);//写文件流
81 //额外的加密操作...
82 }
83 };
84
85
86
87 class BufferedStream : public Stream{
88
89 Stream* stream;//...
90
91 public:
92 BufferedStream(Stream* stm):stream(stm){
93
94 }
95 //...
96 };
97
98
99
100
101
102 void Process(){
103
104 //运行时装配
105 FileStream* s1=new FileStream();
106 CryptoStream* s2=new CryptoStream(s1);
107
108 BufferedStream* s3=new BufferedStream(s1);
109
110 BufferedStream* s4=new BufferedStream(s2);
111
112
113
114 }
实现代码3:
上述实现代码2已经极大地缓解了冗余问题,符合面向对象的设计思想,该轮重构是锦上添花。
重构步骤如下:
考察上述代码,多个子类都有同样的字段(Stream* stream;//...)
应考虑“往上提”,方法有两种,第一种是提到基类(显然不合适,FileStream等并不需要Stream字段 )
所以考虑第二种方法,实现一个“中间类”。
DecoratorStream: public Stream{
protected:
Stream* stream;//...
DecoratorStream(Stream * stm):stream(stm){
}
};
CryptoStream等继承中间类DecoratorStream:
class CryptoStream: public DecoratorStream {
public:
CryptoStream(Stream* stm):DecoratorStream(stm){
}
//...
}
重构完成的最终版本:
FileStream,NetworkStream,MemoryStream等可以创建各自的对象;
但实现加密,缓存功能必须在已有FileStream/NetworkStream等对象基础上;
这些操作本质是扩展操作,也就是“装饰”的含义。
此时类图示意:
这时类的数量为(1 + n + 1 + m)
1 //Decorator3.cpp
2 class Stream{
3
4 public:
5 virtual char Read(int number)=0;
6 virtual void Seek(int position)=0;
7 virtual void Write(char data)=0;
8
9 virtual ~Stream(){}
10 };
11
12 //主体类
13 class FileStream: public Stream{
14 public:
15 virtual char Read(int number){
16 //读文件流
17 }
18 virtual void Seek(int position){
19 //定位文件流
20 }
21 virtual void Write(char data){
22 //写文件流
23 }
24
25 };
26
27 class NetworkStream :public Stream{
28 public:
29 virtual char Read(int number){
30 //读网络流
31 }
32 virtual void Seek(int position){
33 //定位网络流
34 }
35 virtual void Write(char data){
36 //写网络流
37 }
38
39 };
40
41 class MemoryStream :public Stream{
42 public:
43 virtual char Read(int number){
44 //读内存流
45 }
46 virtual void Seek(int position){
47 //定位内存流
48 }
49 virtual void Write(char data){
50 //写内存流
51 }
52
53 };
54
55 //扩展操作
56
57 DecoratorStream: public Stream{
58 protected:
59 Stream* stream;//...
60
61 DecoratorStream(Stream * stm):stream(stm){
62
63 }
64
65 };
66
67 class CryptoStream: public DecoratorStream {
68
69
70 public:
71 CryptoStream(Stream* stm):DecoratorStream(stm){
72
73 }
74
75
76 virtual char Read(int number){
77
78 //额外的加密操作...
79 stream->Read(number);//读文件流
80 }
81 virtual void Seek(int position){
82 //额外的加密操作...
83 stream::Seek(position);//定位文件流
84 //额外的加密操作...
85 }
86 virtual void Write(byte data){
87 //额外的加密操作...
88 stream::Write(data);//写文件流
89 //额外的加密操作...
90 }
91 };
92
93
94
95 class BufferedStream : public DecoratorStream{
96
97 Stream* stream;//...
98
99 public:
100 BufferedStream(Stream* stm):DecoratorStream(stm){
101
102 }
103 //...
104 };
105
106
107
108
109 void Process(){
110
111 //运行时装配
112 FileStream* s1=new FileStream();
113
114 CryptoStream* s2=new CryptoStream(s1);
115
116 BufferedStream* s3=new BufferedStream(s1);
117
118 BufferedStream* s4=new BufferedStream(s2);
119
120
121
122 }
Decorator模式使用动机:
在某些情况下我们可能会“过度地使用继承来扩展对象的功能”,由于基础为类型引入的静态特指,使得这种扩展方式缺乏灵活性;并且随着子类的增多(扩展功能的增多),各个子类的组合(扩展功能的组合)会导致各种子类的膨胀。
模式定义:
动态(组合)地给一个对象增加一些额外的指责。就增加功能而言,Decorator模式比声场子类(继承)更为灵活(消除重复代码&减少子类个数)
类图:
要点总结:
1.通过采用组合并非继承的手法,Decorator模式实现了在运行时动态扩展对象功能的能力,而且可以根据需要扩展多个功能。避免了使用继承带来的”灵活性差“和”多子类衍生问题“
2.Decorator类在接口上表现为is-a Component的继承关系,即Decorator类继承了Component类所具有的接口。但在实现上又表现为has-a Component的组合关系,即Decorator类又使用了另外一个Component类。
3.Decorator模式的目的并非解决”多字类衍生的多继承“问题,Decorator模式应用的要点在于解决”主体类在多个方向上的扩展功能“(显然file,network与加密,缓冲是两种扩展方向)——是为”装饰“的含义。
参考文献:
李建忠老师 《C++设计模式》网络课程
《设计模式:可复用面向对象软件的基础》
原文链接: https://www.cnblogs.com/wangxiaobao/p/5203010.html
欢迎关注
微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍
原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/228943
非原创文章文中已经注明原地址,如有侵权,联系删除
关注公众号【高性能架构探索】,第一时间获取最新文章
转载文章受原作者版权保护。转载请注明原作者出处!