裝飾器模式Decorator(結構型)

1. 概述

       若你從事過面向對象開發,實現給一個類或對象增加行爲,使用繼承機制,這是所有面嚮對象語言的一個基本特性。如果已經存在的一個類缺少某些方法,或者須要給方法添加更多的功能(魅力),你也許會僅僅繼承這個類來產生一個新類—這建立在額外的代碼上。

      通過繼承一個現有類可以使得子類在擁有自身方法的同時還擁有父類的方法。但是這種方法是靜態的,用戶不能控制增加行爲的方式和時機。如果  你希望改變一個已經初始化的對象的行爲,你怎麼辦?或者,你希望繼承許多類的行爲,改怎麼辦?前一個,只能在於運行時完成,後者顯然時可能的,但是可能會導致產生大量的不同的類—可怕的事情。

2. 問題

你如何組織你的代碼使其可以容易的添加基本的或者一些很少用到的 特性,而不是直接不額外的代碼寫在你的類的內部?

3. 解決方案

        裝飾器模式 動態地給一個對象添加一些額外的職責或者行爲。就增加功能來說, Decorator模式相比生成子類更爲靈活。

       裝飾器模式提供了改變子類的靈活方案。裝飾器模式在不必改變原類文件和使用繼承的情況下,動態的擴展一個對象的功能。它是通過創建一個包裝對象,也就是裝飾來包裹真實的對象。

       當用於一組子類時,裝飾器模式更加有用。如果你擁有一族子類(從一個父類派生而來),你需要在與子類獨立使用情況下添加額外的特性,你可以使用裝飾器模式,以避免代碼重複和具體子類數量的增加。

4. 適用性

以下情況使用Decorator模式

1)• 在不影響其他對象的情況下,以動態、透明的方式給單個對象添加職責。

2)• 處理那些可以撤消的職責。

3)• 當不能採用生成子類的方法進行擴充時。一種情況是,可能有大量獨立的擴展,

爲支持每一種組合將產生大量的子類,使得子類數目呈爆炸性增長。

另一種情況可能是因爲類定義被隱藏,或類定義不能用於生成子類。

5. 結構

uml如圖:


6.構建模式的組成

抽象組件角色(Component):定義一個對象接口,以規範準備接受附加責任的對象,

即可以給這些對象動態地添加職責。

具體組件角色(ConcreteComponent) :被裝飾者,定義一個將要被裝飾增加功能的類。

可以給這個類的對象添加一些職責

抽象裝飾器(Decorator):維持一個指向構件Component對象的實例,

並定義一個與抽象組件角色Component接口一致的接口

具體裝飾器角色(ConcreteDecorator):向組件添加職責。

7. 效果

裝飾模式的特點:

       (1) 裝飾對象和真實對象有相同的接口。這樣客戶端對象就可以以和真實對象相同的方式和裝飾對象交互。
  (2) 裝飾對象包含一個真實對象的索引(reference)
  (3) 裝飾對象接受所有的來自客戶端的請求。它把這些請求轉發給真實的對象。

  (4) 裝飾對象可以在轉發這些請求以前或以後增加一些附加功能。這樣就確保了在運行時,不用修改給定對象的結構就可以在外部增加附加的功能。在面向對象的設計中,通常是通過繼承來實現對給定類的功能擴展。

     Decorator模式至少有兩個主要優點和兩個缺點:
1) 比靜態繼承更靈活: 與對象的靜態繼承(多重繼承)相比, Decorator模式提供了更加靈活的向對象添加職責的方式。可以用添加和分離的方法,用裝飾在運行時刻增加和刪除職責。相比之下,繼承機制要求爲每個添加的職責創建一個新的子類。這會產生許多新的類,並且會增加系統的複雜度。此外,爲一個特定的Component類提供多個不同的 Decorator類,這就使得你可以對一些職責進行混合和匹配。使用Decorator模式可以很容易地重複添加一個特性。
2) 避免在層次結構高層的類有太多的特徵 Decorator模式提供了一種“即用即付”的方法來添加職責。它並不試圖在一個複雜的可定製的類中支持所有可預見的特徵,相反,你可以定義一個簡單的類,並且用 Decorator類給它逐漸地添加功能。可以從簡單的部件組合出複雜的功能。這樣,應用程序不必爲不需要的特徵付出代價。同時更易於不依賴於 Decorator所擴展(甚至是不可預知的擴展)的類而獨立地定義新類型的 Decorator。擴展一個複雜類的時候,很可能會暴露與添加的職責無關的細節。
3) Decorator與它的Component不一樣 Decorator是一個透明的包裝。如果我們從對象標識的觀點出發,一個被裝飾了的組件與這個組件是有差別的,因此,使用裝飾不應該依賴對象標識。
4) 有許多小對象 採用Decorator模式進行系統設計往往會產生許多看上去類似的小對象,這些對象僅僅在他們相互連接的方式上有所不同,而不是它們的類或是它們的屬性值有所不同。儘管對於那些瞭解這些系統的人來說,很容易對它們進行定製,但是很難學習這些系統,排錯也很困難。

8. 實現

使用《php設計模式》裏面的例子。

看看以下例子,你可以更好的理解這種觀點。考慮一個建立在組件概念上的“form”表單庫,在那裏你需要爲每一個你想要表現的表單控制類型建立一個類。這種類圖可以如下所示:

        Select and TextInput類是組件類的子類。假如你想要增加一個“labeled”帶標籤的組件—一個輸入表單告訴你要輸入的內容。因爲任何一個表單都可能需要被標記,你可能會象這樣繼承每一個具體的組件:

上面的類圖看起來並不怎麼壞,下面讓我們再增加一些特性。表單驗證階段,你希望能夠指出一個表單控制是否合法。你爲非法控制使用的代碼又一次繼承其它組件,因此又需要產生大量的子類:

這個類看起來並不是太壞,所以讓我們增加一些新的功能。在結構有效性確認中你需要指出結構是否是有效的。你需要讓你檢驗有效性的代碼也可以應用到其它部件,這樣不用再更多的子類上進行有效性驗證。

這裏子類溢出並不是唯一的問題。想一想那些重複的代碼,你需要重新設計你的整個類層次。有沒有更好的方法!確實,裝飾器模式是避免這種情況的好方法。

裝飾器模式結構上類似與代理模式。一個裝飾器對象保留有對對象的引用,而且忠實的重新建立被裝飾對象的公共接口。裝飾器也可以增加方法,擴展被裝飾對象的接口,任意重載方法,甚至可以在腳本執行期間有條件的重載方法。

爲了探究裝飾器模式,讓我們以前面討論過的表單組件庫爲例,並且用裝飾器模式而不是繼承,實現“lable”和“invalidation”兩個特性。

樣本代碼:

組件庫包含哪些特性?

1.        容易創建表單元素

2.        將表單元素以html方式輸出

3.        在每個元素上實現簡單的驗證


本例中,我們創建一個包含姓,名,郵件地址,輸入項的表單。所有的區域都是必須的,而且E-mail必須看起來是有效的E—mail地址。用HTML語言表示,表單的代碼象下面所示:

  1. <form  action=”formpage.php”  method=”post”>  
  2. <b>First  Name:</b>  <input  type=”text”  name=”fname”  value=””><br>  
  3. <b>Last  Name:</b>  <input  type=”text”  name=”lname”  value=””><br>  
  4. <b>Email:</b>  <input  type=”text”  name=”email”  value=””><br>  
  5. <input  type=”submit”  value=”Submit”>  
  6. </form>  

    增加一些css樣式後,表單渲染出來如下圖所示:

     


我們使用裝飾器代碼:

  1. <?php   
  2. /** 
  3.  * 裝飾器模式的組成: 
  4.  * 抽象組件角色(Component):定義一個對象接口,以規範準備接受附加責任的對象,即可以給這些對象動態地添加職責。 
  5.  * 具體組件角色(ConcreteComponent) :被裝飾者,定義一個將要被裝飾增加功能的類。可以給這個類的對象添加一些職責。 
  6.  * 抽象裝飾器(Decorator):維持一個指向構件Component對象的實例,並定義一個與抽象組件角色Component接口一致的接口。 
  7.  * 具體裝飾器角色(ConcreteDecorator): 向組件添加職責。 
  8.  * @author  guisu 
  9.  * @version 1.0 
  10.  */  
  11.   
  12. /** 
  13.  * 抽象組件角色(Component) 
  14.  * 
  15.  */  
  16. class ComponentWidget {  
  17.     function paint() {  
  18.         return $this->_asHtml();  
  19.     }  
  20. }  
  21.   
  22. /** 
  23.  *  
  24.  * 具體組件角色(ConcreteComponent): 
  25.  * 讓我們以一個基本的text輸入組件開始。它(組件)必須要包含輸入區域的名字(name)而且輸入內容可以以HTML的方式渲染。 
  26.  *  
  27.  */  
  28. class ConcreteComponentTextInput extends ComponentWidget {  
  29.   
  30.     protected $_name;  
  31.     protected $_value;  
  32.   
  33.     function TextInput($name$value='') {  
  34.         $this->_name = $name;  
  35.         $this->_value = $value;  
  36.     }  
  37.   
  38.     function _asHtml() {  
  39.         return '<input type="text" name="'.$this->_name.'" value="'.$this->_value.'">';  
  40.   
  41.     }  
  42.   
  43. }  
  44. /** 
  45.  * 抽象裝飾器(Decorator):維持一個指向構件Component對象的實例,並定義一個與抽象組件角色Component接口一致的接口。 
  46.  *  
  47.  * 我們進入有能夠統一增加(一些特性)能力的裝飾器模式。 
  48.  * 作爲開始,我們建立一個普通的可以被擴展產生具體的特定裝飾器的WidgetDecorator類。至少WidgetDecorator類應該能夠在它的構造函數中接受一個組件, 
  49.  * 並複製公共方法paint() 
  50.  * 
  51.  */  
  52. class WidgetDecorator {  
  53.   
  54.     protected $_widget;  
  55.     function __construct( &$widget) {  
  56.         $this->_widget = $widget;  
  57.     }  
  58.     function paint() {  
  59.         return $this->_widget->paint();  
  60.   
  61.     }  
  62.   
  63. }  
  64. /** 
  65.  * 具體裝飾器角色(ConcreteDecorator): 
  66.  * 爲建立一個標籤(lable),需要傳入lable的內容,以及原始的組件 
  67.  * 有標籤的組件也需要複製paint()方法 
  68.  * 
  69.  */  
  70.   
  71.   
  72. class ConcreteDecoratorLabeled extends WidgetDecorator {  
  73.   
  74.     protected $_label;  
  75.   
  76.     function __construct($label, &$widget) {  
  77.         $this->_label = $label;  
  78.         parent::__construct($widget);  
  79.     }  
  80.   
  81.     function paint() {  
  82.         return '<b>'.$this->_label.':</b> '.$this->_widget->paint();  
  83.     }  
  84.   
  85. }  
  86.   
  87.   
  88. /** 
  89.  * 實現 
  90.  * 
  91.  */  
  92. class FormHandler {  
  93.     function build(&$post) {  
  94.         return array(  
  95.         new ConcreteDecoratorLabeled('First Name'new ConcreteComponentTextInput('fname'$post->get('fname')))  
  96.         ,new ConcreteDecoratorLabeled('Last Name'new ConcreteComponentTextInput('lname'$post->get('lname')))  
  97.         ,new ConcreteDecoratorLabeled('Email'new ConcreteComponentTextInput('email'$post->get('email')))  
  98.         );  
  99.   
  100.     }  
  101.   
  102.   
  103.   
  104. }  
  105.   
  106. /** 
  107.  * 通過$_post提交的數據 
  108.  */  
  109.   
  110. class Post {  
  111.   
  112.     private  $store = array();  
  113.   
  114.     function get($key) {  
  115.         if (array_key_exists($key$this->store))  
  116.         return $this->store[$key];  
  117.     }  
  118.   
  119.     function set($key$val) {  
  120.         $this->store[$key] = $val;  
  121.     }  
  122.   
  123.     static function autoFill() {  
  124.         $ret = new self();  
  125.         foreach($_POST as $key => $value) {  
  126.             $ret->set($key$value);  
  127.         }  
  128.         return $ret;  
  129.     }  
  130.   
  131. }  
  132.   
  133.   
  134. ?>  

以創建一個php腳本使用FormHandler類來產生HTML表單:

  1. <form action=”formpage.php” method=”post”>  
  2.   
  3. <?php  
  4. $post =& Post::autoFill();  
  5. $form = FormHandler::build($post);  
  6. foreach($form as $widget) {  
  7.     echo $widget->paint(), "<br>\n";  
  8. }  
  9. ?>  
  10.   
  11. <input type=”submit” value=”Submit”>  
  12.   
  13. </form>  

現在,你已經擁有了個提交給它自身並且能保持posted數據的表單處理(form handler) 類。
現在。我們繼續爲表單添加一些驗證機制。方法是編輯另一個組件裝飾器類來表達一個“invalid”狀態並擴展FormHandler類增加一個validate()方法以處理組件示例數組。如果組件非法(“invalid”),我們通過一個“invalid”類將它包裝在<span>元素中。


  1. <?php  
  2.   
  3. class  Invalid  extends  WidgetDecorator  {  
  4.   
  5.     function  paint()  {  
  6.         return  '<span  class="invalid">'.$this->widget->paint().'</span>';  
  7.     }  
  8. }  

FormHandler新加方法validate:

  1. /** 
  2.  * 實現 
  3.  * 
  4.  */  
  5. class FormHandler {  
  6.     function build(&$post) {  
  7.         return array(  
  8.         new ConcreteDecoratorLabeled('First Name'new ConcreteComponentTextInput('fname'$post->get('fname')))  
  9.         ,new ConcreteDecoratorLabeled('Last Name'new ConcreteComponentTextInput('lname'$post->get('lname')))  
  10.         ,new ConcreteDecoratorLabeled('Email'new ConcreteComponentTextInput('email'$post->get('email')))  
  11.         );  
  12.   
  13.     }  
  14.   
  15.     function  validate(&$form,  &$post)  {  
  16.         $valid  =  true;  
  17.         //  first  name  required  
  18.         if  (!strlen($post->get('fname')))  {  
  19.             $form[0]  =&  new  Invalid($form[0]);  
  20.             $valid  =  false;  
  21.         }  
  22.   
  23.         //  last  name  required  
  24.         if  (!strlen($post->get('lname')))  {  
  25.             $form[1]  =&  new  Invalid($form[1]);  
  26.             $valid  =  false;}  
  27.             //  email  has  to  look  real  
  28.             if  (!preg_match('~\w+@(\w+\.)+\w+~'  
  29.             ,$post->get('email')))  {  
  30.                 $form[2]  =&  new  Invalid($form[2]);  
  31.                 $valid  =  false;  
  32.             }  
  33.             return  $valid;  
  34.   
  35.     }  
  36.   
  37. }  

最後結果:

  1. <html>  
  2.   
  3. <head>  
  4. <title>Decorator  Example</title>  
  5. <style  type="text/css">  
  6. .invalid  {color:  red;  }  
  7. .invalid  input  {  background-color:  red;  color:  yellow;  }  
  8. #myform  input  {  position:  absolute;  left:  110px;  width:  250px;    font-weight:  bold;}  
  9. </style>  
  10. </head>  
  11. <body>  
  12. <form  action="<?php  echo  $_SERVER["PHP_SELF"];  ?>"  method="post">  
  13. <div  id="myform">  
  14. <?php   
  15. $pos  =&  Post::autoFill();  
  16. $form  =  FormHandler::build($post);  
  17. if  ($_POST)  { FormHandler::validate($form,  $post);  
  18. }  
  19. foreach($form  as  $widget)  {  
  20.     echo  $widget->paint(),  "<br>\n";  
  21. }  
  22. ?>  
  23.   
  24. </div>  
  25. <input  type="submit"  value="Submit">  
  26. </form>  
  27. </body>  
  28. </html>  

9. 裝飾器模式與其他相關模式

1)Adapter 模式Decorator模式不同於Adapter模式,因爲裝飾僅改變對象的職責而
不改變它的接口;而適配器將給對象一個全新的接口。

2)Composite模式:可以將裝飾視爲一個退化的、僅有一個組件的組
合。然而,裝飾僅給對象添加一些額外的職責—它的目的不在於對象聚集。

3)Strategy模式:用一個裝飾你可以改變對象的外表;而Strategy模
式使得你可以改變對象的內核。這是改變對象的兩種途徑。


10.總結

1)使用裝飾器設計模式設計類的目標是: 不必重寫任何已有的功能性代碼,而是對某個基於對象應用增量變化。 

2) 裝飾器設計模式採用這樣的構建方式: 在主代碼流中應該能夠直接插入一個或多個更改或“裝飾”目標對象的裝飾器,

同時不影響其他代碼流。

3) Decorator模式採用對象組合而非繼承的手法,實現了在運行時動態的擴展對象功能的能力,

而且可以根據需要擴展多個功能,避免了單獨使用繼承帶來的“靈活性差”和“多子類衍生問題”。

同時它很好地符合面向對象設計原則中“優先使用對象組合而非繼承”和“開放-封閉”原則。

也許裝飾器模式最重要的一個方面是它的超過繼承的能力。“問題”部分展現了一個使用繼承的子類爆炸。

基於裝飾器模式的解決方案,UML類圖展現了這個簡潔靈活的解決方案。

原文地址:http://blog.csdn.net/hguisu/article/details/7531960

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