1. 程式人生 > >c++ 設計模式6 (Decorator 裝飾模式)

c++ 設計模式6 (Decorator 裝飾模式)

ces 軟件組件 類繼承 通過 write height cte 中間 類型

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 裝飾模式)