Decorator 裝飾模式
“單一職責”模式:
- 在軟件組件的設計中,如果責任劃分的不清晰,使用繼承得到的結果往往是隨着需求的變化,子類急劇膨脹,同時充斥着重複代碼,這時候的關鍵是劃清責任
- 典型模式
- Decorator
- Bridge
動機 (Motivation)
- 在某些情況下我們可能會“過度地使用繼承來擴展對象的功能”,由於繼承爲類型引入的靜態特質,使得這種擴展方式缺乏靈活性;並且隨着子類的增多(擴展功能的增多),各種子類的組合(擴展功能的組合)會導致更多子類的膨脹。
- 如何使“對象功能的擴展”能夠根據需要來動態地實現?同時避免“擴展功能的增多”帶來的子類膨脹問題?從而使得任何“功能擴展變化”所導致的影響將爲最低?
舉個例子
- 使用模式前
//業務操作,數據流的讀寫定位操作
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:
//省略
};
//內存數據流
class MemoryStream :public Stream{
public:
//省略
};
//對文件流的加密操作
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:
//省略
};
// 對內存流的加密
class CryptoMemoryStream : public MemoryStream{
public:
//省略
};
//文件流緩存
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();
}
可以看到,因爲主體類在多個方向的擴展(數據流的讀寫定位,以及加密緩存是爲多個方向),當對數據流(文件流,網絡流,內存流)進行操作(加密,緩存)時,此時會派生出大量的類。而在這個過程中的操作有相同。
- 使用模式後
//業務操作
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){
//寫內存流
}
};
- is a Stream 並且 has a Stream
- is a Stream是爲了接口的統一性。
- has a Stream 是爲了充分利用多態性,來消除沒必要的派生類。
既繼承又組合是裝飾模式強大,巧妙的地方。
//裝飾器
DecoratorStream: public Stream{
protected:
Stream* 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);//多態指針stream讀文件流
}
virtual void Seek(int position){
//額外的加密操作...
stream::Seek(position);//定位文件流
//額外的加密操作...
}
virtual void Write(byte data){
//額外的加密操作...
stream::Write(data);//寫文件流
//額外的加密操作...
}
};
class BufferedStream : public DecoratorStream{
public:
BufferedStream(Stream* stm):DecoratorStream(stm){
}
virtual char Read(int number){
//額外的緩存操作...
stream->Read(number);//多態指針stream讀文件流
}
virtual void Seek(int position){
//額外的緩存操作.
stream->Seek(position);//定位流
}
virtual void Write(byte data){
/ /額外的緩存操作.
stream->Write(data);//寫流
}
};
void Process(){
//運行時裝配
FileStream* s1=new FileStream();
CryptoStream* s2=new CryptoStream(s1);//對文件流加密
BufferedStream* s3=new BufferedStream(s1);//緩存文件流
BufferedStream* s4=new BufferedStream(s2);//緩存(加密文件流)
}
結構(Structure)
- 紅色框內是穩定的部分
-
Comonent 就是例子中的Stream類,有讀寫定位行爲。
-
Decorator裝飾器繼承Component,並且組合了一個Component指針.
-
- 藍色框內是變化的部分
-
ConcreteComponent是其子類。(FileStream,NetworkStrem,BufferStream是Stream的子類)
-
ConcreteDecoratorA,(對流加密操作類)(CryptoStream)
-
ConcreteDecoratorB, ( 對流緩存操作類) (BufferedStream)
-
- 在Component進行對應的Operation時(裝飾器也是Component),在子類進行相應的Operation後,因爲裝飾器中還有一個Component指針,還可以對其進行相應的操作也就是component->Operation();從而達到了裝飾作用。就是對裝飾器中的Component指針進行裝飾。
要點總結
- 通過採用組合而非繼承的手法, Decorator模式實現了在運行時動態擴展對象功能的能力,而且可以根據需要擴展多個功能。避免了使用繼承帶來的“靈活性差”和“多子類衍生問題
- Decorator類在接口上表現爲is-a Component的繼承關係,即Decorator類繼承了Component類所具有的接口。但在實現上又表現爲has-a Component的組合關係,即Decorator類又使用了另外一個Component類。
- Decorator模式的目的並非解決“多子類衍生的多繼承”問題,Decorator模式應用的要點在於解決“主體類在多個方向上的擴展功能”——是爲“裝飾”的含義。