1. 程式人生 > >C++設計模式——裝飾模式

C++設計模式——裝飾模式

前言

在實際開發時,你有沒有碰到過這種問題;開發一個類,封裝了一個物件的核心操作,而這些操作就是客戶使用該類時都會去呼叫的操作;而有一些非核心的操作,可能會使用,也可能不會使用;現在該怎麼辦呢?

  1. 將這些非核心的操作全部放到類中,這樣,一個類就包含了很多核心的操作和一些看似有關,但是又無關的操作;這就會使核心類發生“爆炸”的現象,從而使核心類失去了一定的價值,也使使用核心類的客戶在核心操作和非核心操作中掙扎;
  2. 使用繼承來擴充套件核心類,需要使用核心類時,直接建立核心類物件;當需要使用核心類擴充套件類時,就建立核心類擴充套件類物件;這樣貌似是一種很有效的方法;但是由於繼承為型別引入的靜態特質,使得這種擴充套件方式缺乏靈活性;同時,又掉入了另一個陷阱,隨著擴充套件功能的增多,子類也會增多,各種子類的組合,就會導致類的膨脹,最後,就會被淹沒在類的海洋;此時,也不用我多說,你是不是想起了橋接模式,橋接模式就是為了適應多個維度的變化而發生子類“爆炸”的情況,但是,橋接模式是為了適應抽象和實現的不同變化,並不適用於我這裡說的。那如何是好,這就要說到今天總結的裝飾模式了。

什麼是裝飾模式?

在GOF的《設計模式:可複用面向物件軟體的基礎》一書中對裝飾模式是這樣說的:動態地給一個物件新增一些額外的職責。就增加功能來說,Decorator模式相比生成子類更為靈活。

裝飾模式能夠實現動態的為物件新增功能,是從一個物件外部來給物件新增功能。通常給物件新增功能,要麼直接修改物件新增相應的功能,要麼派生對應的子類來擴充套件,抑或是使用物件組合的方式。顯然,直接修改對應的類這種方式並不可取。在面向物件的設計中,而我們也應該儘量使用物件組合,而不是物件繼承來擴充套件和複用功能。裝飾器模式就是基於物件組合的方式,可以很靈活的給物件新增所需要的功能。裝飾器模式的本質就是動態組合。動態是手段,組合才是目的。總之,裝飾模式是通過把複雜的功能簡單化,分散化,然後再執行期間,根據需要來動態組合的這樣一個模式。它使得我們可以給某個物件而不是整個類新增一些功能。

UML類圖

Component:定義一個物件介面,可以給這些物件動態地新增職責;

ConcreteComponent:定義一個具體的Component,繼承自Component,重寫了Component類的虛擬函式;

Decorator:維持一個指向Component物件的指標,該指標指向需要被裝飾的物件;並定義一個與Component介面一致的介面;

ConcreteDecorator:向元件新增職責。

程式碼實現

  1. /*
  2. ** FileName : DecoratorPatternDemo
  3. ** Author : Jelly Young
  4. ** Date : 2013/12/19
  5. ** Description : More information, please go to http://www.jellythink.com
  6. */
  7. #include<iostream>
  8. usingnamespace std;
  9. classComponent
  10. {
  11. public:
  12. virtualvoidOperation()=0;
  13. };
  14. classConcreteComponent:publicComponent
  15. {
  16. public:
  17. voidOperation()
  18. {
  19. cout<<”I am no decoratored ConcreteComponent”<<endl;
  20. }
  21. };
  22. classDecorator:publicComponent
  23. {
  24. public:
  25. Decorator(Component*pComponent): m_pComponentObj(pComponent){}
  26. voidOperation()
  27. {
  28. if(m_pComponentObj != NULL)
  29. {
  30. m_pComponentObj->Operation();
  31. }
  32. }
  33. protected:
  34. Component*m_pComponentObj;
  35. };
  36. classConcreteDecoratorA:publicDecorator
  37. {
  38. public:
  39. ConcreteDecoratorA(Component*pDecorator):Decorator(pDecorator){}
  40. voidOperation()
  41. {
  42. AddedBehavior();
  43. Decorator::Operation();
  44. }
  45. voidAddedBehavior()
  46. {
  47. cout<<”This is added behavior A.”<<endl;
  48. }
  49. };
  50. classConcreteDecoratorB:publicDecorator
  51. {
  52. public:
  53. ConcreteDecoratorB(Component*pDecorator):Decorator(pDecorator){}
  54. voidOperation()
  55. {
  56. AddedBehavior();
  57. Decorator::Operation();
  58. }
  59. voidAddedBehavior()
  60. {
  61. cout<<”This is added behavior B.”<<endl;
  62. }
  63. };
  64. int main()
  65. {
  66. Component*pComponentObj =newConcreteComponent();
  67. Decorator*pDecoratorAOjb =newConcreteDecoratorA(pComponentObj);
  68. pDecoratorAOjb->Operation();
  69. cout<<”=============================================”<<endl;
  70. Decorator*pDecoratorBOjb =newConcreteDecoratorB(pComponentObj);
  71. pDecoratorBOjb->Operation();
  72. cout<<”=============================================”<<endl;
  73. Decorator*pDecoratorBAOjb =newConcreteDecoratorB(pDecoratorAOjb);
  74. pDecoratorBAOjb->Operation();
  75. cout<<”=============================================”<<endl;
  76. delete pDecoratorBAOjb;
  77. pDecoratorBAOjb = NULL;
  78. delete pDecoratorBOjb;
  79. pDecoratorBOjb = NULL;
  80. delete pDecoratorAOjb;
  81. pDecoratorAOjb = NULL;
  82. delete pComponentObj;
  83. pComponentObj = NULL;
  84. }

使用場合

  1. 在不影響其他物件的情況下,以動態的,透明的方式給單個物件新增職責;
  2. 處理那些可以撤銷的職責;
  3. 當不能採用生成子類的方法進行擴充時。一種情況是,可能存在大量獨立的擴充套件,為支援每一種組合將產生大量的子類,使得子類數目呈爆炸性增長。另一種情況可能是因為類定義被隱藏,或類定義不能用於生成子類。

注意事項

  1. 介面的一致性;裝飾物件的介面必須與它所裝飾的Component的介面是一致的,因此,所有的ConcreteDecorator類必須有一個公共的父類;這樣對於使用者來說,就是統一的介面;
  2. 省略抽象的Decorator類;當僅需要新增一個職責時,沒有必要定義抽象Decorator類。因為我們常常要處理,現存的類層次結構而不是設計一個新系統,這時可以把Decorator向Component轉發請求的職責合併到ConcreteDecorator中;
  3. 保持Component類的簡單性;為了保證介面的一致性,元件和裝飾必須要有一個公共的Component類,所以保持這個Component類的簡單性是非常重要的,所以,這個Component類應該集中於定義介面而不是儲存資料。對資料表示的定義應延遲到子類中,否則Component類會變得過於複雜和臃腫,因而難以大量使用。賦予Component類太多的功能,也使得具體的子類有一些它們它們不需要的功能大大增大;

實現要點

  1. Component類在Decorator模式中充當抽象介面的角色,不應該去實現具體的行為。而且Decorator類對於Component類應該透明,換言之Component類無需知道Decorator類,Decorator類是從外部來擴充套件Component類的功能;
  2. Decorator類在介面上表現為“is-a”Component的繼承關係,即Decorator類繼承了Component類所具有的介面。但在實現上又表現為“has-a”Component的組合關係,即Decorator類又使用了另外一個Component類。我們可以使用一個或者多個Decorator物件來“裝飾”一個Component物件,且裝飾後的物件仍然是一個Component物件;
  3. Decortor模式並非解決“多子類衍生的多繼承”問題,Decorator模式的應用要點在於解決“主體類在多個方向上的擴充套件功能”——是為“裝飾”的含義;
  4. 對於Decorator模式在實際中的運用可以很靈活。如果只有一個ConcreteComponent類而沒有抽象的Component類,那麼Decorator類可以是ConcreteComponent的一個子類。如果只有一個ConcreteDecorator類,那麼就沒有必要建立一個單獨的Decorator類,而可以把Decorator和ConcreteDecorator的責任合併成一個類。
  5. Decorator模式的優點是提供了比繼承更加靈活的擴充套件,通過使用不同的具體裝飾類以及這些裝飾類的排列組合,可以創造出很多不同行為的組合;
  6. 由於使用裝飾模式,可以比使用繼承關係需要較少數目的類。使用較少的類,當然使設計比較易於進行。但是,在另一方面,使用裝飾模式會產生比使用繼承關係更多的物件。更多的物件會使得查錯變得困難,特別是這些物件看上去都很相像。

與橋接模式的區別

之前總結了C++設計模式——橋接模式;你會發現,二者都是為了防止過度的繼承,從而造成子類氾濫的情況。那麼二者之間的主要區別是什麼呢?橋接模式的定義是將抽象化與實現化分離(用組合的方式而不是繼承的方式),使得兩者可以獨立變化。可以減少派生類的增長。如果光從這一點來看的話,和裝飾者差不多,但兩者還是有一些比較重要的區別:

  1. 橋接模式中所說的分離,其實是指將結構與實現分離(當結構和實現有可能發生變化時)或屬性與基於屬性的行為進行分離;而裝飾者只是對基於屬性的行為進行封閉成獨立的類,從而達到對其進行裝飾,也就是擴充套件。比如:異常類和異常處理類之間就可以使用橋接模式來實現完成,而不能使用裝飾模式來進行設計;如果對於異常的處理需要進行擴充套件時,我們又可以對異常處理類新增Decorator,從而新增處理的裝飾,達到異常處理的擴充套件,這就是一個橋接模式與裝飾模式的搭配;
  2. 橋接中的行為是橫向的行為,行為彼此之間無關聯,注意這裡的行為之間是沒有關聯的,就比如異常和異常處理之間是沒有行為關聯的一樣;而裝飾者模式中的行為具有可疊加性,其表現出來的結果是一個整體,一個各個行為組合後的一個結果。

總結

裝飾模式重點在裝飾,對核心功能的裝飾作用;將繼承中對子類的擴充套件轉化為功能類的組合,從而將需要對子類的擴充套件轉嫁給使用者去進行呼叫組合,使用者如何組合由使用者去決定。我在學習裝飾模式時,就是重點分析了“裝飾”這個詞,我們都知道,裝飾是在一個核心功能上新增一些附屬功能,從而讓核心功能發揮更大的作用,但是最終它的核心功能是不能丟失的。這就好比我們進行windows shell開發時,我們是對windows的這層殼進行了功能的裝飾,從而實現了我們需要的一些裝飾功能,但是最終的功能還是由windows shell去完成。這就好比,我們的裝飾就是給核心功能添加了一層外衣,讓它看起來更漂亮和完美。