裝飾模式

1.動機

通常有兩種方式能夠實現給一個類或對象增長行爲:ios

  • 繼承機制,使用繼承機制是給現有類添加功能的一種有效途徑,經過繼承一個現有類可使得子類在擁有自身方法的同時還擁有父類的方法。可是這種方法是靜態的,用戶不能控制增長行爲的方式和時機app

  • 關聯機制,即將一個類對象嵌入另外一個對象中,由另外一個對象來決定是否調用嵌入對象的行爲以便擴展本身的行爲,咱們稱這個嵌入的對象爲裝飾器(Decorator)函數

裝飾模式以對客戶透明的方式動態地給一個對象附加上更多的責任,換言之,客戶端並不會以爲對象在裝飾前和裝飾後有什麼不一樣。裝飾模式能夠在不須要創造更多子類的狀況下,將對象的功能加以擴展學習

2.定義

裝飾模式(Decorator Pattern) :動態地給一個對象增長一些額外的職責(Responsibility),就增長對象功能來講,裝飾模式比生成子類實現更爲靈活。其別名也能夠稱爲包裝(Wrapper),與適配器模式的別名相同,但它們適用於不一樣的場合。根據翻譯的不一樣,裝飾模式也有人稱之爲「油漆工模式」,它是一種對象結構型模式。編碼

3.結構

clipboard.png

  • Component:定義一個對象接口,能夠給這些對象動態地添加職責;spa

  • ConcreteComponent:定義一個具體的Component,繼承自Component,重寫了Component類的虛函數;翻譯

  • Decorator:維持一個指向Component對象的指針,該指針指向須要被裝飾的對象;並定義一個與Component接口一致的接口;設計

  • ConcreteDecorator:向組件添加職責。指針

4.代碼分析

實現要點調試

  • Component類在Decorator模式中充當抽象接口的角色,不該該去實現具體的行爲。並且Decorator類對於Component類應該透明,換言之Component類無需知道Decorator類,Decorator類是從外部來擴展Component類的功能;

  • Decorator類在接口上表現爲「is-a」Component的繼承關係,即Decorator類繼承了Component類所具備的接口。但在實現上又表現爲「has-a」Component的組合關係,即Decorator類又使用了另一個Component類。咱們可使用一個或者多個Decorator對象來「裝飾」一個Component對象,且裝飾後的對象仍然是一個Component對象

  • Decortor模式並不是解決「多子類衍生的多繼承」問題,Decorator模式的應用要點在於解決「主體類在多個方向上的擴展功能」——是爲「裝飾」的含義;

  • 對於Decorator模式在實際中的運用能夠很靈活。若是隻有一個ConcreteComponent類而沒有抽象的Component類,那麼Decorator類能夠是ConcreteComponent的一個子類。若是隻有一個ConcreteDecorator類,那麼就沒有必要創建一個單獨的Decorator類,而能夠把Decorator和ConcreteDecorator的責任合併成一個類。

  • Decorator模式的優勢是提供了比繼承更加靈活的擴展,經過使用不一樣的具體裝飾類以及這些裝飾類的排列組合,能夠創造出不少不一樣行爲的組合

  • 因爲使用裝飾模式,能夠比使用繼承關係須要較少數目的類。使用較少的類,固然使設計比較易於進行。可是,在另外一方面,使用裝飾模式會產生比使用繼承關係更多的對象。更多的對象會使得查錯變得困難,特別是這些對象看上去都很相像

#include <iostream>
using namespace std;

class Component
{
public:
    virtual void Operation() = 0;
};

class ConcreteComponent : public Component
{
public:
    void Operation()
    {
        cout << "I am no decoratored ConcreteComponent" << endl;
    }
};

class Decorator : public Component
{
public:
    Decorator(Component *pComponent) : m_pComponentObj(pComponent) {}
    void Operation()
    {
        if (m_pComponentObj != nullptr)
        {
            m_pComponentObj->Operation();//採用多態,運行時所指的動態類型調用對用的版本
        }
    }
protected:
    Component *m_pComponentObj;
};

class ConcreteDecoratorA : public Decorator
{
public:
    ConcreteDecoratorA(Component *pDecorator) : Decorator(pDecorator){}
    void Operation()
    {
        Decorator::Operation();
        AddedBehavior();    
    }
    void  AddedBehavior()
    {
        cout << "This is added behavior A." << endl;
    }
};

class ConcreteDecoratorB : public Decorator
{
public:
    ConcreteDecoratorB(Component *pDecorator) : Decorator(pDecorator){}
    void Operation()
    {    
        Decorator::Operation();
        AddedBehavior();
    }
    void  AddedBehavior()
    {
        cout << "This is added behavior B." << endl;
    }
};
int main()
{
    Component *pComponentObj = new ConcreteComponent();
    Component *pDecoratorAOjb = new ConcreteDecoratorA(pComponentObj);
    pDecoratorAOjb->Operation();
    cout << "=============================================" << endl;

    Component *pDecoratorBOjb = new ConcreteDecoratorB(pComponentObj);
    pDecoratorBOjb->Operation();
    cout << "=============================================" << endl;

    Component *pDecoratorBAOjb = new ConcreteDecoratorB(pDecoratorAOjb);
    pDecoratorBAOjb->Operation();
    cout << "=============================================" << endl;

    delete pDecoratorBAOjb;
    pDecoratorBAOjb = nullptr;

    delete pDecoratorBOjb;
    pDecoratorBOjb = nullptr;

    delete pDecoratorAOjb;
    pDecoratorAOjb = nullptr;

    delete pComponentObj;
    pComponentObj = nullptr;

    system("pause");
    return 0;
}

注意事項

  • 接口的一致性;裝飾對象的接口必須與它所裝飾的Component的接口是一致的,所以,全部的ConcreteDecorator類必須有一個公共的父類;這樣對於用戶來講,就是統一的接口;

  • 省略抽象的Decorator類;當僅須要添加一個職責時,沒有必要定義抽象Decorator類。由於咱們經常要處理,現存的類層次結構而不是設計一個新系統,這時能夠把Decorator向Component轉發請求的職責合併到ConcreteDecorator中;

  • 保持Component類的簡單性;爲了保證接口的一致性,組件和裝飾必需要有一個公共的Component類,因此保持這個Component類的簡單性是很是重要的,因此,這個Component類應該集中於定義接口而不是存儲數據。對數據表示的定義應延遲到子類中,不然Component類會變得過於複雜和臃腫,於是難以大量使用。賦予Component類太多的功能,也使得具體的子類有一些它們它們不須要的功能大大增大

5.模式分析

  • 與繼承關係相比,關聯關係的主要優點在於不會破壞類的封裝性,並且繼承是一種耦合度較大的靜態關係,沒法在程序運行時動態擴展。在軟件開發階段,關聯關係雖然不會比繼承關係減小編碼量,可是到了軟件維護階段,因爲關聯關係使系統具備較好的鬆耦合性,所以使得系統更加容易維護。固然,關聯關係的缺點是比繼承關係要建立更多的對象

  • 使用裝飾模式來實現擴展比繼承更加靈活,它以對客戶透明的方式動態地給一個對象附加更多的責任。裝飾模式能夠在不須要創造更多子類的狀況下,將對象的功能加以擴展

6.優勢

  • 裝飾模式與繼承關係的目的都是要擴展對象的功能,可是裝飾模式能夠提供比繼承更多的靈活性。

  • 能夠經過一種動態的方式來擴展一個對象的功能,經過配置文件能夠在運行時選擇不一樣的裝飾器,從而實現不一樣的行爲

  • 經過使用不一樣的具體裝飾類以及這些裝飾類的排列組合,能夠創造出不少不一樣行爲的組合。可使用多個具體裝飾類來裝飾同一對象,獲得功能更爲強大的對象。

  • 具體構件類與具體裝飾類能夠獨立變化,用戶能夠根據須要增長新的具體構件類和具體裝飾類,在使用時再對其進行組合,原有代碼無須改變,符合「開閉原則」

7.缺點

  • 使用裝飾模式進行系統設計時將產生不少小對象,這些對象的區別在於它們之間相互鏈接的方式有所不一樣,而不是它們的類或者屬性值有所不一樣,同時還將產生不少具體裝飾類。這些裝飾類和小對象的產生將增長系統的複雜度,加大學習與理解的難度。

  • 這種比繼承更加靈活機動的特性,也同時意味着裝飾模式比繼承更加易於出錯,排錯也很困難,對於屢次裝飾的對象,調試時尋找錯誤可能須要逐級排查,較爲煩瑣

8.適用場景

  • 在不影響其餘對象的狀況下,以動態的,透明的方式給單個對象添加職責;

  • 處理那些能夠撤銷的職責;

  • 當不能採用生成子類的方法進行擴充時。一種狀況是,可能存在大量獨立的擴展,爲支持每一種組合將產生大量的子類,使得子類數目呈爆炸性增加。另外一種狀況多是由於類定義被隱藏,或類定義不能用於生成子類。

9.與橋接模式的區別

橋接模式的定義是將抽象化與實現化分離(用組合的方式而不是繼承的方式),使得二者能夠獨立變化。能夠減小派生類的增加。若是光從這一點來看的話,和裝飾者差很少,但二者仍是有一些比較重要的區別:

  • 橋接模式中所說的分離,實際上是指將結構與實現分離(當結構和實現有可能發生變化時)或屬性與基於屬性的行爲進行分離;而裝飾者只是對基於屬性的行爲進行封閉成獨立的類,從而達到對其進行裝飾,也就是擴展。好比:異常類和異常處理類之間就可使用橋接模式來實現完成,而不能使用裝飾模式來進行設計;若是對於異常的處理須要進行擴展時,咱們又能夠對異常處理類添加Decorator,從而添加處理的裝飾,達到異常處理的擴展,這就是一個橋接模式與裝飾模式的搭配;

  • 橋接中的行爲是橫向的行爲,行爲彼此之間無關聯,注意這裏的行爲之間是沒有關聯的,就好比異常和異常處理之間是沒有行爲關聯的同樣;而裝飾者模式中的行爲具備可疊加性,其表現出來的結果是一個總體,一個各個行爲組合後的一個結果

10.總結

  • 裝飾模式用於動態地給一個對象增長一些額外的職責,就增長對象功能來講,裝飾模式比生成子類實現更爲靈活。

  • 裝飾模式包含四個角色:抽象構件定義了對象的接口,能夠給這些對象動態增長職責(方法);具體構件定義了具體的構件對象,實現了在抽象構件中聲明的方法,裝飾器能夠給它增長額外的職責(方法); 抽象裝飾類是抽象構件類的子類,用於給具體構件增長職責,可是具體職責在其子類中實現;具體裝飾類是抽象裝飾類的子類,負責向構件添加新的職責。

  • 裝飾模式的主要優勢在於能夠提供比繼承更多的靈活性,能夠經過一種動態的方式來擴展一個對象的功能,並經過使用不一樣的具體裝飾類以及這些裝飾類的排列組合,能夠創造出不少不一樣行爲的組合,並且具體構件類與具體裝飾類能夠獨立變化,用戶能夠根據須要增長新的具體構件類和具體裝飾類;其主要缺點在於使用裝飾模式進行系統設計時將產生不少小對象,並且裝飾模式比繼承更加易於出錯,排錯也很困難,對於屢次裝飾的對象,調試時尋找錯誤可能須要逐級排查,較爲煩瑣。

  • 裝飾模式適用狀況包括:在不影響其餘對象的狀況下,以動態、透明的方式給單個對象添加職責須要動態地給一個對象增長功能,這些功能也能夠動態地被撤銷當不能採用繼承的方式對系統進行擴充或者採用繼承不利於系統擴展和維護時

相關文章
相關標籤/搜索