c++ 設計模式6 (Decorator 裝飾模式)
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++設計模式》網絡課程
《設計模式:可復用面向對象軟件的基礎》
c++ 設計模式6 (Decorator 裝飾模式)