01 “單一職責”模式:
在軟件組件的設計中,如果責任劃分的不清晰,使用繼承得到的結果往往是隨着需求的變化,子類急劇膨脹,同時充斥着重複代碼,這時候的關鍵是劃清責任。
典型模式
• Decorator
• Bridge
02 Decorator 裝飾模式
001 動機(Motivation)
在某些情況下我們可能會“過度地使用繼承來擴展對象的功能”,由於繼承爲類型引入的靜態特質,使得這種擴展方式缺乏靈活性;並且隨着子類的增多(擴展功能的增多),各種子類的組合(擴展功能的組合)會導致更多子類的膨脹。
如何使“對象功能的擴展”能夠根據需要來動態地實現?同時避免“擴展功能的增多”帶來的子類膨脹問題?從而使得任何“功能擴展變化”所導致的影響將爲最低?
003 模式定義
動態(組合)地給一個對象增加一些額外的職責。就增加功能而言,Decorator模式比生成子類(繼承)更爲靈活(消
除重複代碼 & 減少子類個數)。
——《設計模式》GoF
004 樣例
下面的僞代碼是一個業務操作代碼。讀寫與定位是主體操作,而各種加密是擴展操作。
僞代碼 01
decorator1.cpp
//業務操作
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){
//寫內存流
}
};
//擴展操作
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:
virtual char Read(int number){
//額外的加密操作...
NetworkStream::Read(number);//讀網絡流//**靜態特質**//由繼承實現
}
virtual void Seek(int position){
//額外的加密操作...
NetworkStream::Seek(position);//定位網絡流
//額外的加密操作...
}
virtual void Write(byte data){
//額外的加密操作...
NetworkStream::Write(data);//寫網絡流
//額外的加密操作...
}
};
class CryptoMemoryStream : public MemoryStream{
public:
virtual char Read(int number){
//額外的加密操作...
MemoryStream::Read(number);//讀內存流
}
virtual void Seek(int position){
//額外的加密操作...
MemoryStream::Seek(position);//定位內存流
//額外的加密操作...
}
virtual void Write(byte data){
//額外的加密操作...
MemoryStream::Write(data);//寫內存流
//額外的加密操作...
}
};
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();
}
我們來看一下上述代碼,有以下缺點:
01 在進行擴展操作時,說是繼承主體操作,但是其實和主體操作內容關係不大。
02 在擴展操作中的代碼極其相似,並且繁多,十分繁多。代碼冗餘。來看一下這個代碼結構
上圖就是僞代碼01的代碼結構。來看一下需要多少類,n代表主體操作數,m代表擴展操作數,包括擴展操作相互組合,大概需要1+n*m!/2 多類。這數量十分可怕。這時候我們應該使用裝飾模式,將靜態特質改爲動態特質,使代碼變得更加靈活,消除重複代碼和減少子類數。使用裝飾模式之後優化代碼如下:
僞代碼02
decorator2.cpp
//業務操作
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){
//寫內存流
}
};
//擴展操作
DecoratorStream: public Stream{//public Stream這個繼承是爲了完善接口符合規範的目的
//用組合的方式來支持多態的一個變化
protected:
Stream* stream;//... 其實這個指針指向了FileStream、NetworkStream、MemoryStream
//上面的組合是爲了支持調用那些Stream實現類
DecoratorStream(Stream * stm):stream(stm){
}
};
class CryptoStream: public DecoratorStream {
public:
CryptoStream(Stream* stm):DecoratorStream(stm){
}
virtual char Read(int number){
//額外的加密操作...
stream->Read(number);//讀文件流//動態特質//由組合實現
}
virtual void Seek(int position){
//額外的加密操作...
stream::Seek(position);//定位文件流
//額外的加密操作...
}
virtual void Write(byte data){
//額外的加密操作...
stream::Write(data);//寫文件流
//額外的加密操作...
}
};
class BufferedStream : public DecoratorStream{
Stream* stream;//...
public:
BufferedStream(Stream* stm):DecoratorStream(stm){
}
//...
};
//主體操作和擴展操作應該分支分開繼承
void Process(){
//**運行時裝配**
FileStream* s1=new FileStream**()**;
CryptoStream* s2=new CryptoStream(**s1**);
BufferedStream* s3=new BufferedStream(**s1)**;
BufferedStream* s4=new BufferedStream(**s2**);//擴展組合
}
如上代碼,我們用一個抽象的基類:DecoratorStream: public Stream繼承了Stream保證了接口的規範性,然後用一個動態指針Stream* stream;來支持調用stream各種實現類。記住擴展類是在主體類的基礎上實現,如僞代碼02的void Process中的黑體標記。
這個是上述代碼的結構。主體類和擴展類分開分支繼承。總共需要1+n+1+m個類即可實現。
05 結構(Structure)
依然如前面幾個模式一樣,紅色畫圈代表穩定的,藍色畫圈代表變化的。
07 要點總結
通過採用組合而非繼承的手法, Decorator模式實現了在運行時動態擴展對象功能的能力,而且可以根據需要擴展多個功能。避免了使用繼承帶來的“靈活性差”和“多子類衍生問題”。
Decorator類在接口上表現爲is-a Component的繼承關係,即Decorator類繼承了Component類所具有的接口。但在實現上又表現爲has-a Component的組合關係,即Decorator類又使用了另外一個Component類。
(這第二點總結也是識別裝飾模式的一個主要特徵,既繼承、又組合)
DecoratorStream: public Stream{//public Stream這個繼承是爲了完善接口符合規範的目的
//用組合的方式來支持多態的一個變化
protected:
Stream* stream;//... 其實這個指針指向了FileStream、NetworkStream、MemoryStream
//上面的組合是爲了支持調用那些Stream實現類
DecoratorStream(Stream * stm):stream(stm){
}
};
Decorator模式的目的並非解決“多子類衍生的多繼承”問題,Decorator模式應用的要點在於解決“主體類在多個方向上的擴展功能”——是爲“裝飾”的含義。