【設計模式】06 Decorator 裝飾模式(“單一職責”模式一)

01 “單一職責”模式:

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

典型模式
• Decorator
• Bridge

02 Decorator 裝飾模式

001 動機(Motivation)

在某些情況下我們可能會“過度地使用繼承來擴展對象的功能”,由於繼承爲類型引入的靜態特質,使得這種擴展方式缺乏靈活性;並且隨着子類的增多(擴展功能的增多),各種子類的組合(擴展功能的組合)會導致更多子類的膨脹。

如何使“對象功能的擴展”能夠根據需要來動態地實現?同時避免“擴展功能的增多”帶來的子類膨脹問題?從而使得任何“功能擴展變化”所導致的影響將爲最低?

003 模式定義

動態(組合)地給一個對象增加一些額外的職責。就增加功能而言,Decorator模式比生成子類(繼承)更爲靈活(消
除重複代碼 & 減少子類個數)。
——《設計模式》GoF

004 樣例

下面的僞代碼是一個業務操作代碼。讀寫與定位是主體操作,而各種加密是擴展操作。

僞代碼 01

decorator1.cpp
//業務操作
class Stream{
publicvirtual 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{

publicvirtual 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模式應用的要點在於解決“主體類在多個方向上的擴展功能”——是爲“裝飾”的含義。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章