本文最后编辑于 前,其中的内容可能需要更新。
单一职责模式
在软件组件的设计中,如果责任划分的不清晰,使用继承得到的结果往往是随着需求的变化,子类急剧膨胀,同时充斥着重复代码,这时候的关键是划清责任
1.装饰模式
1.1动机
为某些情况下我们可能会”过度地使用继承来扩展对象的功能“,由于继承为类型引入的静态性质,使得这种拓展方式缺乏灵活性;并且随着子类的增多(拓展功能的增多),各种子类的组合(拓展功能的组合)会导致更多子类的膨胀
如何使”对象功能的拓展”能够根据需要来动态的实现?同时避免”拓展功能的增多”带来子类膨胀问题?从而使得”功能拓展变化”导致的影响降为最低?
1.2 讲解
假如我们要写一个对流的操作,首先我们定义一个流的接口
1 2 3 4 5 6
| class Stream { public: virtual char Read(int numeber)=0; virtual void Seek(int position)=0; virtual void Write(char data)=0; };
|
然后我们又有网络流,文件流,内存流,如下代码
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
| class FileStream : public Stream { public: virtual char Read(int number) { } virtual void Seek(int position) { } virtual void Write(int data) { } };
class NetworkStream : public Stream { public: virtual char Read(int number) { } virtual void Seek(int position) { } virtual void Write(int data) { } };
class MemoryStream : public Stream { public: virtual char Read(int number) { } virtual void Seek(int position) { } virtual void Write(int data) { } };
|
如果我们要写一个对流的加密,可能会像下面的写法一样
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
| class CryptoFileStream : public FileStream{ public: virtual char Read(int number) { FileStream::Read(numnber); } virtual void Seek(int position) { FileStream::Seek(position); } virtual void Write(int data) { FileStream::Write(data); } };
class CryptoFileStream : public NetworkStream{ public: virtual char Read(int number) { NetworkStream::Read(numnber); } virtual void Seek(int position) { NetworkStream::Seek(position); } virtual void Write(int data) { NetworkStream::Write(data); } };
class CryptoFileStream : public MemoryStream{ public: virtual char Read(int number) { MemoryStream::Read(numnber); } virtual void Seek(int position) { MemoryStream::Seek(position); } virtual void Write(int data) { MemoryStream::Write(data); } };
|
观察上面的代码,我们发现其实对FileStream, NetworkStream和MemoryStream的加密过程都是一样的,唯一不同的是FileStream::Read(number)
和NetworkStream::Read(number)
这类代码。
这是因为继承的不当使用,错误的使用继承为我们引入了静态特性,也就是FileStream::Read(number)
和NetworkStream::Read(number)
,你必须为此指定是哪个类的Read
操作。也就是说,我们应该使用组合来解决这个问题。如下
而且这样写代码还有一个问题,如下图
如果Stream接口发送一个改变,首先假设FileStream, NetworkStream和MemoryStream有n个变化,FileStream的子类有m个变化,由于子类可以有组合关系,那么将会有在Crypto所在的层次m!/2的变化,最后总共有1+n+(n * m!/2)的变化要改,m小的时候还有,m一大起来我们要付出的代价太大了
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
| class CryptoFileStream { FileStream *stream; public: virtual char Read(int number) { stream->Read(numnber); } virtual void Seek(int position) { stream->Seek(position); } virtual void Write(int data) { stream->Write(data); } };
class CryptoFileStream{ NetworkStream *stream public: virtual char Read(int number) { stream->Read(numnber); } virtual void Seek(int position) { stream->Seek(position); } virtual void Write(int data) { stream->Write(data); } };
class CryptoFileStream{ MemoryStream *stream; public: virtual char Read(int number) { stream->Read(numnber); } virtual void Seek(int position) { stream->Seek(position); } virtual void Write(int data) { stream->Write(data); } };
|
我们可以发现,这三个类这样处理后长得差不多,事实上我们完全可以这样处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class CryptoStream { Stream *stream; public: virtual char Read(int number) { stream->Read(numnber); } virtual void Seek(int position) { stream->Seek(position); } virtual void Write(int data) { stream->Write(data); } };
|
将三个类合并起来,然后FileStream *stream
, NetworkStream *stream
和MemoryStream *stream
可以用Stream* stream
来表示,因为这三个类都是Stream的子类,完全可以利用多态在运行时进行不同的处理
但这样又有一个问题,即是virtual的这些函数怎么来的?所以我们还需要继承Stream接口。
基于上面的代码和刚才的继承图,我们同样可以设计出BufferStream
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
| class CryptoStream : public Stream { Stream *stream; public: CryptoStream(Stream *stm) : stream(stm) { } virtual char Read(int number) { stream->Read(numnber); } virtual void Seek(int position) { stream->Seek(position); } virtual void Write(int data) { stream->Write(data); } };
class BufferStream : public Stream { Stream *stream; public: BufferStream(Stream *stm) : stream(stm) { } virtual char Read(int number) { stream->Read(numnber); } virtual void Seek(int position) { stream->Seek(position); } virtual void Write(int data) { stream->Write(data); } };
|
接下来,两个类都有一个Stream的成员,我们应该往上提或者在此加一个中间类,但往父类提有个问题,Stream的子类不都使用Stream* stream这个成员。所以我们设置一个中间类
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
| 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(numnber); } virtual void Seek(int position) { stream->Seek(position); } virtual void Write(int data) { stream->Write(data); } };
class BufferStream : public DecoratorStream { public: BufferStream(Stream *stm) : DecoratorStream(stm) { } virtual char Read(int number) { stream->Read(numnber); } virtual void Seek(int position) { stream->Seek(position); } virtual void Write(int data) { stream->Write(data); } };
|
最后的继承图如下所示,Stream一个改变,File Stream,NetworkStream和MemoryStream有n个变化,CrypoStream和BufferStream,此时没有个组合,又不依赖三个Stream子类,所有只有m+1个改变。总共就只有1+n+(1+m)的改变
1.3 模式定义
动态(组合)地给一个对象增加一些额外地职责。就增加功能而言,Decorator模式比生成子类(继承)更为灵活(消除重复代码&减少子类个数)
1.4 结构
1.5 要点总结
- 通过采用组合而非继承地方式,Decorator模式实现了在运行时动态拓展对象功能的能力,而且可以根据需要拓展多个功能。避免了使用继承带来的”灵活性差“和”多子类衍生问题“
- Decorator类在接口上表现为is-a Component的继承关系,但在实现上又变现为has-a Component的组合关系(同时有is-a和has-a的时候,要考虑是不是用了decorator模式)
- Decorator模式的目的并非为了解决”多子类衍生的多继承问题“,Decorator模式应用的要点在于解决”主体类在多个方向上的拓展功能“——是为”装饰“的含义