設計模式--裝飾器模式Decorator(結構型)

benbenxiongyuan發表於2014-04-10

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>  
<form  action=”formpage.php”  method=”post”>
<b>First  Name:</b>  <input  type=”text”  name=”fname”  value=””><br>
<b>Last  Name:</b>  <input  type=”text”  name=”lname”  value=””><br>
<b>Email:</b>  <input  type=”text”  name=”email”  value=””><br>
<input  type=”submit”  value=”Submit”>
</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 
/**
 * 裝飾器模式的組成:
 * 抽象元件角色(Component):定義一個物件介面,以規範準備接受附加責任的物件,即可以給這些物件動態地新增職責。
 * 具體元件角色(ConcreteComponent) :被裝飾者,定義一個將要被裝飾增加功能的類。可以給這個類的物件新增一些職責。
 * 抽象裝飾器(Decorator):維持一個指向構件Component物件的例項,並定義一個與抽象元件角色Component介面一致的介面。
 * 具體裝飾器角色(ConcreteDecorator): 向元件新增職責。
 * @author  guisu
 * @version 1.0
 */

/**
 * 抽象元件角色(Component)
 *
 */
class ComponentWidget {
	function paint() {
		return $this->_asHtml();
	}
}

/**
 * 
 * 具體元件角色(ConcreteComponent):
 * 讓我們以一個基本的text輸入元件開始。它(元件)必須要包含輸入區域的名字(name)而且輸入內容可以以HTML的方式渲染。
 * 
 */
class ConcreteComponentTextInput extends ComponentWidget {

	protected $_name;
	protected $_value;

	function TextInput($name, $value='') {
		$this->_name = $name;
		$this->_value = $value;
	}

	function _asHtml() {
		return '<input type="text" name="'.$this->_name.'" value="'.$this->_value.'">';

	}

}
/**
 * 抽象裝飾器(Decorator):維持一個指向構件Component物件的例項,並定義一個與抽象元件角色Component介面一致的介面。
 * 
 * 我們進入有能夠統一增加(一些特性)能力的裝飾器模式。
 * 作為開始,我們建立一個普通的可以被擴充套件產生具體的特定裝飾器的WidgetDecorator類。至少WidgetDecorator類應該能夠在它的建構函式中接受一個元件,
 * 並複製公共方法paint()
 *
 */
class WidgetDecorator {

	protected $_widget;
	function __construct( &$widget) {
		$this->_widget = $widget;
	}
	function paint() {
		return $this->_widget->paint();

	}

}
/**
 * 具體裝飾器角色(ConcreteDecorator):
 * 為建立一個標籤(lable),需要傳入lable的內容,以及原始的元件
 * 有標籤的元件也需要複製paint()方法
 *
 */


class ConcreteDecoratorLabeled extends WidgetDecorator {

	protected $_label;

	function __construct($label, &$widget) {
		$this->_label = $label;
		parent::__construct($widget);
	}

	function paint() {
		return '<b>'.$this->_label.':</b> '.$this->_widget->paint();
	}

}


/**
 * 實現
 *
 */
class FormHandler {
	function build(&$post) {
		return array(
		new ConcreteDecoratorLabeled('First Name', new ConcreteComponentTextInput('fname', $post->get('fname')))
		,new ConcreteDecoratorLabeled('Last Name', new ConcreteComponentTextInput('lname', $post->get('lname')))
		,new ConcreteDecoratorLabeled('Email', new ConcreteComponentTextInput('email', $post->get('email')))
		);

	}



}

/**
 * 通過$_post提交的資料
 */

class Post {

	private  $store = array();

	function get($key) {
		if (array_key_exists($key, $this->store))
		return $this->store[$key];
	}

	function set($key, $val) {
		$this->store[$key] = $val;
	}

	static function autoFill() {
		$ret = new self();
		foreach($_POST as $key => $value) {
			$ret->set($key, $value);
		}
		return $ret;
	}

}


?>

以建立一個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>  
<form action=”formpage.php” method=”post”>

<?php
$post =& Post::autoFill();
$form = FormHandler::build($post);
foreach($form as $widget) {
	echo $widget->paint(), "<br>\n";
}
?>

<input type=”submit” value=”Submit”>

</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. }  
<?php

class  Invalid  extends  WidgetDecorator  {

	function  paint()  {
		return  '<span  class="invalid">'.$this->widget->paint().'</span>';
	}
}

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. }  
/**
 * 實現
 *
 */
class FormHandler {
	function build(&$post) {
		return array(
		new ConcreteDecoratorLabeled('First Name', new ConcreteComponentTextInput('fname', $post->get('fname')))
		,new ConcreteDecoratorLabeled('Last Name', new ConcreteComponentTextInput('lname', $post->get('lname')))
		,new ConcreteDecoratorLabeled('Email', new ConcreteComponentTextInput('email', $post->get('email')))
		);

	}

	function  validate(&$form,  &$post)  {
		$valid  =  true;
		//  first  name  required
		if  (!strlen($post->get('fname')))  {
			$form[0]  =&  new  Invalid($form[0]);
			$valid  =  false;
		}

		//  last  name  required
		if  (!strlen($post->get('lname')))  {
			$form[1]  =&  new  Invalid($form[1]);
			$valid  =  false;}
			//  email  has  to  look  real
			if  (!preg_match('~\w+@(\w+\.)+\w+~'
			,$post->get('email')))  {
				$form[2]  =&  new  Invalid($form[2]);
				$valid  =  false;
			}
			return  $valid;

	}

}

最後結果:

  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>  
<html>

<head>
<title>Decorator  Example</title>
<style  type="text/css">
.invalid  {color:  red;  }
.invalid  input  {  background-color:  red;  color:  yellow;  }
#myform  input  {  position:  absolute;  left:  110px;  width:  250px;    font-weight:  bold;}
</style>
</head>
<body>
<form  action="<?php  echo  $_SERVER["PHP_SELF"];  ?>"  method="post">
<div  id="myform">
<?php 
$pos  =&  Post::autoFill();
$form  =  FormHandler::build($post);
if  ($_POST)  { FormHandler::validate($form,  $post);
}
foreach($form  as  $widget)  {
	echo  $widget->paint(),  "<br>\n";
}
?>

</div>
<input  type="submit"  value="Submit">
</form>
</body>
</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

相關文章