【設計模式】裝飾模式
能力要求:抽象能力 + 業務知識
設計要結合具體的業務,如果業務知識不夠,常常容易出現設計過度的現象——那些不常變的地方,簡單即可。事實上,很多良好的設計都是迭代而來的,設計並非一成不變,如果從一開始就想設計好,很容易陷入過度設計。
Q:設計一個類,其職責是將資料輸出到檔案。
Class Iout
{
Public:
int outToFile();
……
};
如果想增加一個輸出到網路的功能的呢?
再增加一個函式?——不符合開閉原則
輸出操作作為一個基類,然後具體輸出到哪裡,由子類去實現?——可行
如果需要壓縮後再輸出呢?如果還需要加密再輸出呢?如果需要先加密再壓縮再輸出呢?
……
顯然,我們可以通過繼承的方式來給原物件增加新功能,但是面對那麼多的組合方式,將產生大量的子類!
此時可以選擇使用裝飾者模式,動態地給別的物件增加額外的職責,它採用組合的方式比生成子類更加靈活。
類圖:
各個角色介紹:
抽象構件(Component):給出一個抽象介面,以規範準備接收附加責任的物件。
具體構件(Concrete Component):定義一個將要接收附加責任的類。
裝飾(Decorator):持有一個構件(Component)物件的例項,並定義一個與抽象構件介面一致的介面。
具體裝飾(Concrete Decorator):負責給構件物件”貼上”附加的責任。
實現的例子:
//抽象類Iout
class Iout
{
public:
virtual void out() = 0;
public:
virtual ~Iout()
{
cout<<"In the destructor of Iout"<<endl;
}
};
//具體類 FileOut
class FileOut: public Iout
{
public:
void out()
{
cout<<"Out to file."<<endl;
}
public :
virtual ~FileOut()
{
cout<<"In the destructor of FileOut"<<endl;
}
};
//具體類NetOut
class NetOut:public Iout
{
public:
void out()
{
cout<<"Out to network."<<endl;
}
public:
virtual ~NetOut()
{
cout<<"In the destructor of NetOut"<<endl;
}
};
//抽象類,Decorator
class Decorator:public Iout
{
protected:
Iout* myOut;
public:
Decorator(Iout* myOut):myOut(myOut) {} //具體的輸出的裝飾類
virtual ~Decorator()
{
cout<<"In the destructor of Decorator"<<endl;
}
public:
void out()
{
myOut->out();
}
};
//壓縮裝飾類
class CompressDecorator: public Decorator
{
public:
CompressDecorator(Iout* myOut):Decorator(myOut) {}
virtual ~CompressDecorator()
{
cout<<"in the destructor of CompressDecorator"<<endl;
}
public:
void compress()
{
cout<<"Do compress."<<endl;
}
void out()
{
compress();
myOut->out();
}
};
//加密裝飾類
class EncryptDecorator:public Decorator
{
public:
EncryptDecorator(Iout* myOut):Decorator(myOut) {}
~EncryptDecorator()
{
cout<<"in the destructor of EncryptDecorator"<<endl;
}
public:
void encrypt()
{
cout<<"Do encrypt."<<endl;
}
public:
void out()
{
encrypt();
myOut->out();
}
};
int main(int argc, char **argv)
{
//給FileOut增加壓縮功能
Iout* fileOut(new FileOut);
Iout* compressdFileOut(new CompressDecorator(fileOut));
compressdFileOut->out();
cout<<endl;
cout<<endl<<"---------------"<<endl;
//給NetOut增加加密、壓縮功能
Iout* netOut(new NetOut);
//增加加密
Iout* encryptdNetOut(new EncryptDecorator(netOut));
//增加壓縮
Iout* compressdAndEncryptdFileOut(new CompressDecorator(encryptdNetOut));
compressdAndEncryptdFileOut->out();
cout<<endl;
cout<<endl<<"--------------"<<endl;
return 0;
}
適用場景:
- 需要擴充套件一個類的功能,或給一個類增加附加責任。
- 需要動態地給一個物件增加功能,這些功能可以再動態地撤銷。
- 需要增加由一些基本功能的排列組合而產生的非常大量的功能。
優點:
- Decorator模式與繼承關係的目的都是要擴充套件物件的功能,但是Decorator可以提供比繼承更多的靈活性。
- 通過使用不同的具體裝飾類以及這些裝飾類的排列組合,設計師可以創造出很多不同行為的組合。
缺點:
- 更靈活的同時也意味著更多的複雜性。
- 裝飾模式會導致設計中出現許多小類,如果過度使用,會使程式變得很複雜。
- 裝飾模式是針對抽象元件(Component)型別程式設計。但是,如果你要針對具體元件程式設計時,就應該重新思考裝飾者是否合適。當然也可以改變Component介面,增加新的公開的行為,實現“半透明”的裝飾者模式。
進階:半透明裝飾模式
一般情況下,純粹的裝飾模式很少用到,大部分情況下用到的都是半透明的裝飾模式。
在透明裝飾模式中,要求客戶端完全針對抽象程式設計,裝飾模式的透明性要求客戶端程式不應該將物件宣告為具體構件型別或具體裝飾型別,而應該全部宣告為抽象構件型別。對於客戶端而言,具體構件物件和具體裝飾物件沒有任何區別。
也就是應該使用如下程式碼:
Component c, c1; //使用抽象構件型別定義物件
c = new ConcreteComponent();
c1 = new ConcreteDecorator (c);
而不應該使用如下程式碼:
ConcreteComponent c; //使用具體構件型別定義物件
c = new ConcreteComponent();
或
ConcreteDecorator c1; //使用具體裝飾型別定義物件
c1 = new ConcreteDecorator(c);
透明裝飾模式可以讓客戶端透明地使用裝飾之前的物件和裝飾之後的物件(我們前面的例子中使用的都是Iout),無須關心它們的區別,此外,還可以對一個已裝飾過的物件進行多次裝飾,得到更為複雜、功能更為強大的物件。
有些時候,我們為了能夠呼叫到新增的方法,我們不得不用具體裝飾型別來定義裝飾之後的物件,而具體構件型別還是可以使用抽象構件型別來定義,這種裝飾模式即為半透明裝飾模式,也就是說,對於客戶端而言,具體構件型別無須關心,是透明的;但是具體裝飾型別必須指定,這是不透明的。
比如在前面的例子中,我們想要單獨使用新增的壓縮功能(而不輸出),客戶端程式碼片段如下所示:
……
Iout* fileOut(new FileOut); //使用抽象構件型別定義
CompressDecorator * compressdFileOut(new CompressDecorator(fileOut)); //使用具體裝飾型別定義
compressdFileOut-> compress(); //可以單獨使用新介面
……
其他:
1. 如果只有一個ConcreteComponent類而沒有抽象的Component類(介面),那麼Decorator類經常可以是ConcreteComponent的一個子類;
2. 如果只有一個ConcreteDecorator類,那麼就沒有必要建立一個單獨的Decorator類,而可以把Decorator和ConcreteDecorator的責任合併成一個類。