標籤:查看 基礎上 new char 代碼 物件導向 技術分享 參考文獻 本質
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 裝飾模式)