“单一职责”模式:在软件组件的设计中,如果责任划分的不清晰,使用继承得到的结果往往是随着需求的变化,子类急剧膨胀,同时 充斥着重复代码,这时候的关键是划清责任 。
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 (); }
这里通过继承添加功能的方式“编译时装配”,可以观察到:
这样创建出来的类数量非常多,达到了1 + n + n ⋅ m ! 2 1+n+n \cdot \frac{m!}{2} 1 + n + n ⋅ 2 m !
n n n 是第二层流的种类数,m m m 是扩展的功能数。
第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 { Stream* stream; 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); }
编译时没有 CryptoFileStream
、BufferedFileStream
这样的类,但是可以运行时通过组合的方式把它们装配起来,符合我们的要求。
应当优先使用对象组合,那么就在 CryptoStream
和 BufferedStream
中添加 *Stream
。
根据《重构》:当一个变量都声明为某个类型子类的类型,那么把变量都声明为这个类型 。
故在 CryptoStream
和 BufferedStream
中加入的应该是基类 Stream
。
CryptoStream
和 BufferedStream
这两个扩展类既包含了 Stream
字段,又继承了 Stream
方法,这是为了后面virtual函数接口规范,能正常调用。
通过组合获得 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 + m 1+n+1+m 1 + n + 1 + m 。
结构
Component 对应 Stream
ConcreteComponent 对应 FileStream, MemoryStream, NetwordStream
Decorator 对应 DecoratorStream
ConcreteDecorator 对应 CryptoStream, BufferedStream
总结
通过采用组合而非继承的手法,Decorator模式实现了在运行时动态扩展对象功能的能力,并且可以根据需要扩展多个功能。避免了使用继承带来的“灵活性差”和“多子类衍生问题”。
Decorator 类在接口上表现为 is-a Component 的继承关系,记Decorator类继承了 Component 类所具有的接口。但在实现上又表现为 has-a Component 的组合关系,即 Decorator 类又失恋了另一个 Component 类。
如果看到一个类父类是某类,里面又有那个类字段, 那么就可以高度怀疑是 Decorator 模式。
有时可能看不到内部字段,但可以根据类的外部接口来判断:父类是某类,构造器参数也是那个类。
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; 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 () {} }; 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 () { } }; 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); }
这里省略了构造函数。
结构
Abstraction 相当于 Messager,内含 MessagerImp 指针
Implementor 相当于 MessagerImp
RefinedAbstraction 相当于 Lite, Perfect 版本
ConcreteImplementor 相当于 PC, Mobile 版本
总结
Bridge 模式使用“对象间的组合关系 (使用实现基类部分的指针)”解耦了抽象和实现之间的固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化。所谓抽象和实现沿着各自的维度变化,即“子类化”他们。
Bridge模式有时候类似多继承方案,但是多继承方案往往违背单一职责原则(即一个类只有一个变化的原因),复用性比较差,Bridge模式是比多继承方案更好的解决方法。
Bridge模式的应用一般在“两个非常强的变化维度”,有时一个类也有多于两个的变化维度,这时也可以使用 Bridge 扩展模式。