動態地給一個對象增加一些額外的職責,就增加對象的功能來說,裝飾模式比生成子類更為靈活。裝飾模式是一種對象結構模式。
我們常常通過繼承的方式來對一個既有的類進行功能添加,但繼承方式有顯著的局限性,因為
繼承具有侵入性繼承是一種is a
的關系,具有強耦合性,難以復用代碼。
例如在窗口控件當中,要增加新的功能如增加滾動條,增加背景圖片,通過繼承的方式來增加新的功能,有下面的解決方案。
可以看到在這個解決方案中,我們分別將Scroll
和BckGnd
的功能實現了三遍。其根本原因是繼承是一種強耦合的關系,我們無法把BckGndTextBox
中的BckGnd
部分拿出來給ListBox
使用。於是我們期待一種模式,可以像繼承這樣方便地增加新的功能,並且能夠方便地復用代碼,這種模式真實存在—裝飾模式。
其實裝飾模式的設計理念很簡單,就是利用聚合將要被添加功能的類聚合到用於添加功能的類的內部,正如裝飾模式的名字一樣,用一個新的類將被裝飾的類包裝起來。
假設我們將需要被裝飾(添加功能)的類稱作Component
而用於裝飾(添加功能)的類稱作Decorator
。這樣,我們需要在Decorator
內部聚合一個Component
,然後再將新的功能和舊的功能組合成為一個調用鏈,現在代碼大致如下:
class Component {
public:
Component (){};
virtual ~Component ();
virtual void operate(){};
};
class Decorator {
private:
Component* component;
void addOperate():
public:
Decorator (Component* c):component(c){};
virtual ~Decorator ();
virtual void operate(){
addOperate();
component->operate()
}
};
現在我們已經為Component
類添加了新的功能,可是這樣做問題很明顯,添加了新功能之後的Decorator
根本無法用在那些原本使用Component
的地方。OK那麼我們就讓Decorator
成為一個Component
類,這樣就可以解決這個問題了。現在代碼修改如下:
class Component {
public:
Component (){};
virtual ~Component ();
virtual void operate(){};
};
class Decorator : public Component{
private:
Component* component;
void addOperate():
public:
Decorator (Component* c):component(c){};
virtual ~Decorator ();
virtual void operate() override{
addOperate();
component->operate()
}
};
int main()
{
Component *A = new Component;
Component *B = new Decorator(A);
Component *C = new Decorator(B);
Component *D = new Decorator(C);
Component *E = new Decorator(D);
Component *F = new Decorator(E);
F->operate();
}
可是問題又出現了,我們如何靈活地改變需要添加的新功能呢?也就是addOperate
?我們可以將addOperate
寫成一個虛函數,將這個職責推遲到子類當中去。修改後的代碼如下(代碼1):
class Component {
public:
Component (){};
virtual ~Component (){};
virtual void operate(){
std::cout << "Component::operate" << std::endl;
};
};
class Decorator : public Component{
private:
Component* component;
protected:
virtual void addOperate(){};
public:
Decorator (Component* c):component(c){};
virtual ~Decorator (){};
virtual void operate() override{
addOperate();
component->operate();
}
};
class DecoratorB : public Decorator{
protected:
virtual void addOperate() override{
std::cout << "DecoratorB::addOperate" << std::endl;
};
public:
DecoratorB (Component* c):Decorator(c){};
virtual ~DecoratorB (){};
};
class DecoratorA : public Decorator{
protected:
virtual void addOperate() override{
std::cout << "DecoratorA::addOperate" << std::endl;
};
public:
DecoratorA (Component* c):Decorator(c){};
virtual ~DecoratorA (){};
};
int main()
{
Component *A = new Component;
Component *B = new DecoratorA(A);
Component *C = new DecoratorB(B);
Component *D = new DecoratorA(C);
Component *E = new DecoratorB(D);
Component *F = new DecoratorA(E);
F->operate();
}
運行結果:
DecoratorA::addOperate
DecoratorB::addOperate
DecoratorA::addOperate
DecoratorB::addOperate
DecoratorA::addOperate
Component::operate
這時總歸不方便,因為父類Decorator
中規定了接口addOperate
,子類不想使用這個接口作為添加的新功能都不行,假設子類要添加一個addState
和addCounter
之類的功能呢?子類必須實現addState
和addCounter
然後利用addOperate
調用這兩個方法,這樣的做法有點繞,干脆將addOperate
的聲明也一並推遲到子類中讓子類來決定這個接口該是什麼,但是推遲了addOperate
的聲明,必然也會推遲對其的調用,這個調用依舊放在virtual void operate
中。修改後的代碼如下(代碼2):
class Component {
public:
Component (){};
virtual ~Component (){};
virtual void operate(){
std::cout << "Component::operate" << std::endl;
}
};
class Decorator : public Component{
private:
Component *_component;
public:
Decorator (Component *C):_component(C){}
virtual ~Decorator (){delete _component;}
virtual void operate() override {
_component->operate();
}
};
class DecoratorA : public Decorator{
private:
void addBehave(){
std::cout << "DecoratorA:addBehave" << std::endl;
}
public:
DecoratorA(Component *D):Decorator(D){}
virtual void operate () override{
Decorator::operate();
addBehave();
}
};
class DecoratorB : public Decorator{
private:
void addBehave(){
std::cout << "DecoratorB:addBehave" << std::endl;
}
public:
DecoratorB(Component *D):Decorator(D){}
virtual void operate () override{
Decorator::operate();
addBehave();
}
};
以上的代碼就是裝飾模式的示例代碼。代碼2就是“正統”的裝飾模式實現,但我個人更喜歡代碼1中的實現,因為在增加新的功能的時候,不需要修改operate
方法,這個向外提供的方法,早早地固定好了,子類越少改動它,出錯的幾率就越小。
下面給出裝飾模式的類圖和Widget的整體方案
裝飾模式的優待點:
可以拓展一個對象的功能而不用繼承,十分方便靈活,不會造成類的個數大量增加可以動態拓展對象的功能具體的構件類和裝飾類可以獨立變化,增加新的功能的時候不需要修改既有的類,符合開閉原則。裝飾模式的缺點:
邏輯上比繼承復雜,出錯可能性比繼承大裝飾模式的適用場景:
需要動態拓展對象的功能時不能使用繼承來增加新功能時