1. 程式人生 > 實用技巧 >設計模式 - 裝飾模式

設計模式 - 裝飾模式

“單一職責”模式

在軟體元件的設計中,如果責任劃分的不清晰,使用繼承得到的結果往往是隨著需求的變化,子類急劇膨脹,同時充斥著重複程式碼,這時候的關鍵是劃清責任。

典型模式
Decorator
Bridge

程式碼示例:

  1 //業務操作
  2 class Stream{
  3 public  4     virtual char Read(int number)=0;
  5     virtual void Seek(int position)=0;
  6     virtual void Write(char data)=0;
  7     
  8     virtual
~Stream(){} 9 }; 10 11 //主體類 12 class FileStream: public Stream{ 13 public: 14 virtual char Read(int number){ 15 //讀檔案流 16 } 17 virtual void Seek(int position){ 18 //定位檔案流 19 } 20 virtual void Write(char data){ 21 //寫檔案流 22 } 23 24 }; 25 26
class NetworkStream :public Stream{ 27 public: 28 virtual char Read(int number){ 29 //讀網路流 30 } 31 virtual void Seek(int position){ 32 //定位網路流 33 } 34 virtual void Write(char data){ 35 //寫網路流 36 } 37 38 }; 39 40 class MemoryStream :public Stream{
41 public: 42 virtual char Read(int number){ 43 //讀記憶體流 44 } 45 virtual void Seek(int position){ 46 //定位記憶體流 47 } 48 virtual void Write(char data){ 49 //寫記憶體流 50 } 51 52 }; 53 54 //擴充套件操作 55 class CryptoFileStream :public FileStream{ 56 public: 57 virtual char Read(int number){ 58 59 //額外的加密操作... 60 FileStream::Read(number);//讀檔案流 61 62 } 63 virtual void Seek(int position){ 64 //額外的加密操作... 65 FileStream::Seek(position);//定位檔案流 66 //額外的加密操作... 67 } 68 virtual void Write(byte data){ 69 //額外的加密操作... 70 FileStream::Write(data);//寫檔案流 71 //額外的加密操作... 72 } 73 }; 74 75 class CryptoNetworkStream : :public NetworkStream{ 76 public: 77 virtual char Read(int number){ 78 79 //額外的加密操作... 80 NetworkStream::Read(number);//讀網路流 81 } 82 virtual void Seek(int position){ 83 //額外的加密操作... 84 NetworkStream::Seek(position);//定位網路流 85 //額外的加密操作... 86 } 87 virtual void Write(byte data){ 88 //額外的加密操作... 89 NetworkStream::Write(data);//寫網路流 90 //額外的加密操作... 91 } 92 }; 93 94 class CryptoMemoryStream : public MemoryStream{ 95 public: 96 virtual char Read(int number){ 97 98 //額外的加密操作... 99 MemoryStream::Read(number);//讀記憶體流 100 } 101 virtual void Seek(int position){ 102 //額外的加密操作... 103 MemoryStream::Seek(position);//定位記憶體流 104 //額外的加密操作... 105 } 106 virtual void Write(byte data){ 107 //額外的加密操作... 108 MemoryStream::Write(data);//寫記憶體流 109 //額外的加密操作... 110 } 111 }; 112 113 class BufferedFileStream : public FileStream{ 114 //... 115 }; 116 117 class BufferedNetworkStream : public NetworkStream{ 118 //... 119 }; 120 121 class BufferedMemoryStream : public MemoryStream{ 122 //... 123 } 124 125 126 127 128 class CryptoBufferedFileStream :public FileStream{ 129 public: 130 virtual char Read(int number){ 131 132 //額外的加密操作... 133 //額外的緩衝操作... 134 FileStream::Read(number);//讀檔案流 135 } 136 virtual void Seek(int position){ 137 //額外的加密操作... 138 //額外的緩衝操作... 139 FileStream::Seek(position);//定位檔案流 140 //額外的加密操作... 141 //額外的緩衝操作... 142 } 143 virtual void Write(byte data){ 144 //額外的加密操作... 145 //額外的緩衝操作... 146 FileStream::Write(data);//寫檔案流 147 //額外的加密操作... 148 //額外的緩衝操作... 149 } 150 }; 151 152 153 154 void Process(){ 155 156 //編譯時裝配 157 CryptoFileStream *fs1 = new CryptoFileStream(); 158 159 BufferedFileStream *fs2 = new BufferedFileStream(); 160 161 CryptoBufferedFileStream *fs3 =new CryptoBufferedFileStream(); 162 163 }
decorator1.cpp

流stream作為一個基類有一些基礎的方法,如read,write,seek

各種流檔案流,網路流,記憶體流等繼承於基類流,同時會有擴充套件的需求,如加密,且加密相同,只是讀取的操作不一樣,因此又會有新的不同流的擴充套件類繼承於各種流。當然除了加密還會有其他的擴充套件操作如緩衝buffer,同理也可以像加密一樣繼承。

如果要對一種流既加密又緩衝,可以繼承了流之後在讀寫方法中新增加密操作,緩衝,讀流的基礎操作。

以上的程式碼會有什麼缺陷?

隨著擴充套件方法的增加,由於對繼承的不良使用,子類的規模不斷膨脹,產生很多重複的程式碼。

改進一:使用組合

  1 //業務操作
  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 class CryptoStream: public Stream {
 57     
 58     Stream* stream;//...
 59 
 60 public:
 61     CryptoStream(Stream* stm):stream(stm){
 62     
 63     }
 64     
 65     
 66     virtual char Read(int number){
 67        
 68         //額外的加密操作...
 69         stream->Read(number);//讀檔案流
 70     }
 71     virtual void Seek(int position){
 72         //額外的加密操作...
 73         stream::Seek(position);//定位檔案流
 74         //額外的加密操作...
 75     }
 76     virtual void Write(byte data){
 77         //額外的加密操作...
 78         stream::Write(data);//寫檔案流
 79         //額外的加密操作...
 80     }
 81 };
 82 
 83 
 84 
 85 class BufferedStream : public Stream{
 86     
 87     Stream* stream;//...
 88     
 89 public:
 90     BufferedStream(Stream* stm):stream(stm){
 91         
 92     }
 93     //...
 94 };
 95 
 96 
 97 void Process(){
 98 
 99     //執行時裝配
100     FileStream* s1=new FileStream();
101     CryptoStream* s2=new CryptoStream(s1);
102     
103     BufferedStream* s3=new BufferedStream(s1);
104     
105     BufferedStream* s4=new BufferedStream(s2);
106     
107     
108 
109 }
decorator2.cpp

若多個子類含有相同的欄位,那應該將該欄位向上層提。

改進二:於是在建立一箇中間類Decorator

  1 //業務操作
  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 void Process(){
107 
108     //執行時裝配
109     FileStream* s1=new FileStream();
110     
111     CryptoStream* s2=new CryptoStream(s1);
112     
113     BufferedStream* s3=new BufferedStream(s1);
114     
115     BufferedStream* s4=new BufferedStream(s2);
116     
117     
118 
119 }
decorator3.cpp

在DecoratorStream中,使用組合的方式實現多型,包含了Stream基類。

裝飾模式,動態地給一個物件新增一些額外的職責。就增加功能來說,裝飾模式相比生成子類更靈活。有時我們希望給某個物件而不是整個類新增一些功能。例如上面的例子,有一個檔案流,允許對它新增加密或者緩衝的擴充套件功能。裝飾模式下,會將檔案流嵌入到另一個物件(Decorator)中,由該物件完成特性的新增,該嵌入的物件就成為裝飾,這個裝飾與它所裝飾的元件介面一致,因此它對使用該元件的客戶透明。

動機

  • 在某些情況下我們可能會“過度地使用繼承來擴充套件物件的功能”,由於繼承為型別引入的靜態特質,使得這種擴充套件方式缺乏靈活性;並且隨著子類的增多(擴充套件功能的增多),各種子類的組合(擴充套件功能的組合)會導致更多子類的膨脹。
  • 如何使“物件功能的擴充套件”能夠根據需要來動態地實現?同時避免“擴充套件功能的增多”帶來的子類膨脹問題?從而使得任何“功能擴充套件變化”所導致的影響將為最低?

繼承引入的靜態特性如下第七行:

 1 //擴充套件操作
 2 class CryptoFileStream :public FileStream{
 3 public:
 4     virtual char Read(int number){
 5        
 6         //額外的加密操作...
 7         FileStream::Read(number);//讀檔案流
 8     }
 9     ...
10 }

組合引入的動態特性如下第八行:

 1 class CryptoStream: public DecoratorStream {
 2 
 3 public:
 4     CryptoStream(Stream* stm):DecoratorStream(stm){
 5     
 6     }
 7     
 8     virtual char Read(int number){
 9         //額外的加密操作...
10         stream->Read(number);//讀檔案流
11     }
12     ...
13 }

模式定義

動態(組合)地給一個物件增加一些額外的職責。就增加功能而言,Decorator模式比生成子類(繼承)更為靈活(消除重複程式碼 & 減少子類個數)。

                              ——《設計模式》GoF

要點總結

  • 通過採用組合而非繼承的手法, Decorator模式實現了在執行時動態擴充套件物件功能的能力,而且可以根據需要擴充套件多個功能。避免了使用繼承帶來的“靈活性差”和“多子類衍生問題”
  • Decorator類在介面上表現為is-a Component的繼承關係,即Decorator類繼承了Component類所具有的介面。但在實現上又表現為has-a Component的組合關係,即Decorator類又使用了另外一個Component類
  • Decorator模式的目的並非解決“多子類衍生的多繼承”問題,Decorator模式應用的要點在於解決“主體類在多個方向上的擴充套件功能”——是為“裝飾”的含義

(若一個類繼承了一個父類,且在欄位裡包含(組合)了該父類,很大可能就是Decorator設計模式,基類和組合在功能上由類似的地方,一般來說不會在一個類中同時繼承和組合同一個類。但在裝飾模式中,使用繼承是為了完善基類stream介面的規範,使用組合是為了支援呼叫filestream,netstream中的介面)

裝飾模式提供了更加靈活的向物件新增職責的方式。可以用新增和分離的方法,用裝飾在執行時刻增加和刪除職責。裝飾模式提供了一種“即用即付”的方法來新增職責。它並不試圖在一個複雜的可定製的類中支援所有可預見的特徵,相反,你可以定義一個簡單的類,並且用裝飾類給它逐漸地新增功能。可以從簡單的部件組合出複雜的功能