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

1. 概述

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

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

2. 問題

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

3. 解決方案

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

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

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

4. 適用性

以下情況使用Decorator模式

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

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

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

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

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

5.
結構

uml如圖:

1.jpg

6.構建模式的組成

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

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

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

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

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

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

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

7. 效果

裝飾模式的特點:

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

  (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”帶標簽的組件—一個輸入表單告訴你要輸入的內容。因為任何一個表單都可能需要被標記,你可能會象這樣繼承每一個具體的組件:

2.jpg

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

3.jpg

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

4.jpg

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

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

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

樣本代碼:

組件庫包含哪些特性?

1.  容易創建表單元素

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

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

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

<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樣式后,表單渲染出來如下圖所示:

     5.jpg

我們使用裝飾器代碼:

<?php   
/** 
 * 裝飾器模式的組成: 
 * 抽象組件角色(Component):定義一個對象接口,以規范準備接受附加責任的對象,即可以給這些對象動態地添加職責。 
 * 具體組件角色(ConcreteComponent) :被裝飾者,定義一個將要被裝飾增加功能的類??梢越o這個類的對象添加一些職責。 
 * 抽象裝飾器(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表單:

<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>元素中。
<?php  
  
class  Invalid  extends  WidgetDecorator  {  
  
    function  paint()  {  
        return  '<span  class="invalid">'.$this->widget->paint().'</span>';  
    }  
}

FormHandler新加方法validate:

/** 

 * 實現 

 * 

 */  

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;  

  

    }  

  

}  

最后結果:

<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

原創文章,作者:s19930811,如若轉載,請注明出處:http://www.www58058.com/2936

(0)
s19930811s19930811
上一篇 2015-07-01
下一篇 2015-07-03

相關推薦

  • 磁盤陣列RAID

    磁盤陣列RAID 什么是RAID RAID的全稱是Redundant Array of Inexpensive Disks 可以釋義為 廉價磁盤冗余陣列,后來的RAID里的字母I被認為是Independent,即獨立的磁盤冗余陣列。 RAID的作用 由于服務器中磁盤讀寫率太大,所以磁盤經常損壞,raid可以讓壞了一塊磁盤時,服務器不至于巖機。當然這只是其中的…

    Linux干貨 2017-06-18
  • 關于nginx狀態監控字段的個人見解(求真相)

    今天看到了馬哥視頻其中一節對nginx狀態監控信息的介紹,對視頻ppt上的監控字段解析產生了一些疑問,ppt內容如下: active connections – 活躍的連接數量server accepts handled requests — 總共處理了xxx個連接 , 成功創建xxx次握手, 總共處理了xxx個請求reading — 讀取客戶端的連接數.w…

    Linux干貨 2016-07-12
  • ansible進階(roles應用)

    ansible 進階 一、roles簡介 一個項目從開始到結束,不是簡單幾十個playbook就可以完事了,當文件數很多,有上百個的話,僅通過簡單的includes不停的引用,那最終的結果錯綜復雜。這個時候ansible roles就可以很好的發揮它的作用了。 roles,字面意思是角色的含義,可以理解為有相互關聯功能的集合。我們把安裝ntp、mem、ngi…

    2017-01-05
  • N25第六周 vim,crontab命令的使用

    請詳細總結vim編輯器的使用并完成以下練習題 vim是一款功能強大的文本編輯器,是程序員的必備神器。 vim工作模式分為三種:編輯模式,輸入模式,末行模式,三種工作模式可以進行來自由切換     編輯模式—》輸入模式: 直接鍵i      輸入模式—》…

    Linux干貨 2016-12-29
  • openssl關于CA證書的創建

    1、用openssl實現證書申請 先在/etc/pki/CA/目錄下創建一個index.txt的文件,作為ca證書的數據庫 在相同目錄下創建一個serial的序列號文件,并寫入01 生成ca的簽名證書用到的私鑰文件 注意:私鑰的權限時600,文件名必須是cakey.pem 生成自簽證書 這樣私有CA建立完成 申請認證: 在申請的機器上生成私鑰 生成申請文件 …

    Linux干貨 2016-09-23
  • 系統管理之Systemd詳解(centos7)

    這篇著重講解下Syetemd的相關知識,systemd可以說是centos7上的重大改革,功能之強大媲美一個操作系統,那下面就從以下幾點來進行講解:CentOS7啟動Unit介紹服務管理和查看啟動排錯破解口令修復grub2 啟動流程: post–>BISO–>bootloader(MBR)–>kernel(ramdisk)–>…

    Linux干貨 2016-09-21
欧美性久久久久