本文實(shí)例講述了PHP設(shè)計(jì)模式:裝飾器模式Decorator。分享給大家供大家參考,具體如下:
1. 概述
若你從事過(guò)面向?qū)ο箝_(kāi)發(fā),實(shí)現(xiàn)給一個(gè)類(lèi)或?qū)ο笤黾有袨?,使用繼承機(jī)制,這是所有面向?qū)ο笳Z(yǔ)言的一個(gè)基本特性。如果已經(jīng)存在的一個(gè)類(lèi)缺少某些方法,或者須要給方法添加更多的功能(魅力),你也許會(huì)僅僅繼承這個(gè)類(lèi)來(lái)產(chǎn)生一個(gè)新類(lèi)—這建立在額外的代碼上。
通過(guò)繼承一個(gè)現(xiàn)有類(lèi)可以使得子類(lèi)在擁有自身方法的同時(shí)還擁有父類(lèi)的方法。但是這種方法是靜態(tài)的,用戶不能控制增加行為的方式和時(shí)機(jī)。如果 你希望改變一個(gè)已經(jīng)初始化的對(duì)象的行為,你怎么辦?或者,你希望繼承許多類(lèi)的行為,改怎么辦?前一個(gè),只能在于運(yùn)行時(shí)完成,后者顯然時(shí)可能的,但是可能會(huì)導(dǎo)致產(chǎn)生大量的不同的類(lèi)—可怕的事情。
2. 問(wèn)題
你如何組織你的代碼使其可以容易的添加基本的或者一些很少用到的 特性,而不是直接不額外的代碼寫(xiě)在你的類(lèi)的內(nèi)部?
3. 解決方案
裝飾器模式: 動(dòng)態(tài)地給一個(gè)對(duì)象添加一些額外的職責(zé)或者行為。就增加功能來(lái)說(shuō), Decorator模式相比生成子類(lèi)更為靈活。
裝飾器模式提供了改變子類(lèi)的靈活方案。裝飾器模式在不必改變?cè)?lèi)文件和使用繼承的情況下,動(dòng)態(tài)的擴(kuò)展一個(gè)對(duì)象的功能。它是通過(guò)創(chuàng)建一個(gè)包裝對(duì)象,也就是裝飾來(lái)包裹真實(shí)的對(duì)象。
當(dāng)用于一組子類(lèi)時(shí),裝飾器模式更加有用。如果你擁有一族子類(lèi)(從一個(gè)父類(lèi)派生而來(lái)),你需要在與子類(lèi)獨(dú)立使用情況下添加額外的特性,你可以使用裝飾器模式,以避免代碼重復(fù)和具體子類(lèi)數(shù)量的增加。
4. 適用性
以下情況使用Decorator模式
1)• 在不影響其他對(duì)象的情況下,以動(dòng)態(tài)、透明的方式給單個(gè)對(duì)象添加職責(zé)。
2)• 處理那些可以撤消的職責(zé)。
3)• 當(dāng)不能采用生成子類(lèi)的方法進(jìn)行擴(kuò)充時(shí)。一種情況是,可能有大量獨(dú)立的擴(kuò)展,
為支持每一種組合將產(chǎn)生大量的子類(lèi),使得子類(lèi)數(shù)目呈爆炸性增長(zhǎng)。
另一種情況可能是因?yàn)轭?lèi)定義被隱藏,或類(lèi)定義不能用于生成子類(lèi)。
5. 結(jié)構(gòu)
uml如圖:

6.構(gòu)建模式的組成
抽象組件角色(Component):定義一個(gè)對(duì)象接口,以規(guī)范準(zhǔn)備接受附加責(zé)任的對(duì)象,
即可以給這些對(duì)象動(dòng)態(tài)地添加職責(zé)。
具體組件角色(ConcreteComponent) :被裝飾者,定義一個(gè)將要被裝飾增加功能的類(lèi)。
可以給這個(gè)類(lèi)的對(duì)象添加一些職責(zé)
抽象裝飾器(Decorator):維持一個(gè)指向構(gòu)件Component對(duì)象的實(shí)例,
并定義一個(gè)與抽象組件角色Component接口一致的接口
具體裝飾器角色(ConcreteDecorator):向組件添加職責(zé)。
7. 效果
裝飾模式的特點(diǎn):
?。?) 裝飾對(duì)象和真實(shí)對(duì)象有相同的接口。這樣客戶端對(duì)象就可以以和真實(shí)對(duì)象相同的方式和裝飾對(duì)象交互。
(2) 裝飾對(duì)象包含一個(gè)真實(shí)對(duì)象的索引(reference)
?。?) 裝飾對(duì)象接受所有的來(lái)自客戶端的請(qǐng)求。它把這些請(qǐng)求轉(zhuǎn)發(fā)給真實(shí)的對(duì)象。
?。?) 裝飾對(duì)象可以在轉(zhuǎn)發(fā)這些請(qǐng)求以前或以后增加一些附加功能。這樣就確保了在運(yùn)行時(shí),不用修改給定對(duì)象的結(jié)構(gòu)就可以在外部增加附加的功能。在面向?qū)ο蟮脑O(shè)計(jì)中,通常是通過(guò)繼承來(lái)實(shí)現(xiàn)對(duì)給定類(lèi)的功能擴(kuò)展。
Decorator模式至少有兩個(gè)主要優(yōu)點(diǎn)和兩個(gè)缺點(diǎn):
1) 比靜態(tài)繼承更靈活: 與對(duì)象的靜態(tài)繼承(多重繼承)相比, Decorator模式提供了更加靈活的向?qū)ο筇砑勇氊?zé)的方式。可以用添加和分離的方法,用裝飾在運(yùn)行時(shí)刻增加和刪除職責(zé)。相比之下,繼承機(jī)制要求為每個(gè)添加的職責(zé)創(chuàng)建一個(gè)新的子類(lèi)。這會(huì)產(chǎn)生許多新的類(lèi),并且會(huì)增加系統(tǒng)的復(fù)雜度。此外,為一個(gè)特定的Component類(lèi)提供多個(gè)不同的 Decorator類(lèi),這就使得你可以對(duì)一些職責(zé)進(jìn)行混合和匹配。使用Decorator模式可以很容易地重復(fù)添加一個(gè)特性。
2) 避免在層次結(jié)構(gòu)高層的類(lèi)有太多的特征 Decorator模式提供了一種“即用即付”的方法來(lái)添加職責(zé)。它并不試圖在一個(gè)復(fù)雜的可定制的類(lèi)中支持所有可預(yù)見(jiàn)的特征,相反,你可以定義一個(gè)簡(jiǎn)單的類(lèi),并且用 Decorator類(lèi)給它逐漸地添加功能??梢詮暮?jiǎn)單的部件組合出復(fù)雜的功能。這樣,應(yīng)用程序不必為不需要的特征付出代價(jià)。同時(shí)更易于不依賴(lài)于 Decorator所擴(kuò)展(甚至是不可預(yù)知的擴(kuò)展)的類(lèi)而獨(dú)立地定義新類(lèi)型的 Decorator。擴(kuò)展一個(gè)復(fù)雜類(lèi)的時(shí)候,很可能會(huì)暴露與添加的職責(zé)無(wú)關(guān)的細(xì)節(jié)。
3) Decorator與它的Component不一樣 Decorator是一個(gè)透明的包裝。如果我們從對(duì)象標(biāo)識(shí)的觀點(diǎn)出發(fā),一個(gè)被裝飾了的組件與這個(gè)組件是有差別的,因此,使用裝飾不應(yīng)該依賴(lài)對(duì)象標(biāo)識(shí)。
4) 有許多小對(duì)象 采用Decorator模式進(jìn)行系統(tǒng)設(shè)計(jì)往往會(huì)產(chǎn)生許多看上去類(lèi)似的小對(duì)象,這些對(duì)象僅僅在他們相互連接的方式上有所不同,而不是它們的類(lèi)或是它們的屬性值有所不同。盡管對(duì)于那些了解這些系統(tǒng)的人來(lái)說(shuō),很容易對(duì)它們進(jìn)行定制,但是很難學(xué)習(xí)這些系統(tǒng),排錯(cuò)也很困難。
8. 實(shí)現(xiàn)
使用《php設(shè)計(jì)模式》里面的例子。
看看以下例子,你可以更好的理解這種觀點(diǎn)??紤]一個(gè)建立在組件概念上的“form”表單庫(kù),在那里你需要為每一個(gè)你想要表現(xiàn)的表單控制類(lèi)型建立一個(gè)類(lèi)。這種類(lèi)圖可以如下所示:
Select and TextInput類(lèi)是組件類(lèi)的子類(lèi)。假如你想要增加一個(gè)“l(fā)abeled”帶標(biāo)簽的組件—一個(gè)輸入表單告訴你要輸入的內(nèi)容。因?yàn)槿魏我粋€(gè)表單都可能需要被標(biāo)記,你可能會(huì)象這樣繼承每一個(gè)具體的組件:

上面的類(lèi)圖看起來(lái)并不怎么壞,下面讓我們?cè)僭黾右恍┨匦?。表單?yàn)證階段,你希望能夠指出一個(gè)表單控制是否合法。你為非法控制使用的代碼又一次繼承其它組件,因此又需要產(chǎn)生大量的子類(lèi):

這個(gè)類(lèi)看起來(lái)并不是太壞,所以讓我們?cè)黾右恍┬碌墓δ?。在結(jié)構(gòu)有效性確認(rèn)中你需要指出結(jié)構(gòu)是否是有效的。你需要讓你檢驗(yàn)有效性的代碼也可以應(yīng)用到其它部件,這樣不用再更多的子類(lèi)上進(jìn)行有效性驗(yàn)證。

這里子類(lèi)溢出并不是唯一的問(wèn)題。想一想那些重復(fù)的代碼,你需要重新設(shè)計(jì)你的整個(gè)類(lèi)層次。有沒(méi)有更好的方法!確實(shí),裝飾器模式是避免這種情況的好方法。
裝飾器模式結(jié)構(gòu)上類(lèi)似與代理模式。一個(gè)裝飾器對(duì)象保留有對(duì)對(duì)象的引用,而且忠實(shí)的重新建立被裝飾對(duì)象的公共接口。裝飾器也可以增加方法,擴(kuò)展被裝飾對(duì)象的接口,任意重載方法,甚至可以在腳本執(zhí)行期間有條件的重載方法。
為了探究裝飾器模式,讓我們以前面討論過(guò)的表單組件庫(kù)為例,并且用裝飾器模式而不是繼承,實(shí)現(xiàn)“l(fā)able”和“invalidation”兩個(gè)特性。
樣本代碼:
組件庫(kù)包含哪些特性?
1. 容易創(chuàng)建表單元素
2. 將表單元素以html方式輸出
3. 在每個(gè)元素上實(shí)現(xiàn)簡(jiǎn)單的驗(yàn)證
本例中,我們創(chuàng)建一個(gè)包含姓,名,郵件地址,輸入項(xiàng)的表單。所有的區(qū)域都是必須的,而且E-mail必須看起來(lái)是有效的E—mail地址。用HTML語(yǔ)言表示,表單的代碼象下面所示:
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樣式后,表單渲染出來(lái)如下圖所示:

我們使用裝飾器代碼:
?php
/**
* 裝飾器模式的組成:
* 抽象組件角色(Component):定義一個(gè)對(duì)象接口,以規(guī)范準(zhǔn)備接受附加責(zé)任的對(duì)象,即可以給這些對(duì)象動(dòng)態(tài)地添加職責(zé)。
* 具體組件角色(ConcreteComponent) :被裝飾者,定義一個(gè)將要被裝飾增加功能的類(lèi)??梢越o這個(gè)類(lèi)的對(duì)象添加一些職責(zé)。
* 抽象裝飾器(Decorator):維持一個(gè)指向構(gòu)件Component對(duì)象的實(shí)例,并定義一個(gè)與抽象組件角色Component接口一致的接口。
* 具體裝飾器角色(ConcreteDecorator): 向組件添加職責(zé)。
* @author guisu
* @version 1.0
*/
/**
* 抽象組件角色(Component)
*
*/
class ComponentWidget {
function paint() {
return $this->_asHtml();
}
}
/**
*
* 具體組件角色(ConcreteComponent):
* 讓我們以一個(gè)基本的text輸入組件開(kāi)始。它(組件)必須要包含輸入?yún)^(qū)域的名字(name)而且輸入內(nèi)容可以以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):維持一個(gè)指向構(gòu)件Component對(duì)象的實(shí)例,并定義一個(gè)與抽象組件角色Component接口一致的接口。
*
* 我們進(jìn)入有能夠統(tǒng)一增加(一些特性)能力的裝飾器模式。
* 作為開(kāi)始,我們建立一個(gè)普通的可以被擴(kuò)展產(chǎn)生具體的特定裝飾器的WidgetDecorator類(lèi)。至少WidgetDecorator類(lèi)應(yīng)該能夠在它的構(gòu)造函數(shù)中接受一個(gè)組件,
* 并復(fù)制公共方法paint()
*
*/
class WidgetDecorator {
protected $_widget;
function __construct( $widget) {
$this->_widget = $widget;
}
function paint() {
return $this->_widget->paint();
}
}
/**
* 具體裝飾器角色(ConcreteDecorator):
* 為建立一個(gè)標(biāo)簽(lable),需要傳入lable的內(nèi)容,以及原始的組件
* 有標(biāo)簽的組件也需要復(fù)制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();
}
}
/**
* 實(shí)現(xiàn)
*
*/
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')))
);
}
}
/**
* 通過(guò)$_post提交的數(shù)據(jù)
*/
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;
}
}
?>
以創(chuàng)建一個(gè)php腳本使用FormHandler類(lèi)來(lái)產(chǎn)生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>
現(xiàn)在,你已經(jīng)擁有了個(gè)提交給它自身并且能保持posted數(shù)據(jù)的表單處理(form handler) 類(lèi)。
現(xiàn)在。我們繼續(xù)為表單添加一些驗(yàn)證機(jī)制。方法是編輯另一個(gè)組件裝飾器類(lèi)來(lái)表達(dá)一個(gè)“invalid”狀態(tài)并擴(kuò)展FormHandler類(lèi)增加一個(gè)validate()方法以處理組件示例數(shù)組。如果組件非法(“invalid”),我們通過(guò)一個(gè)“invalid”類(lèi)將它包裝在span>元素中。
?php
class Invalid extends WidgetDecorator {
function paint() {
return 'span class="invalid">'.$this->widget->paint().'/span>';
}
}
FormHandler新加方法validate:
/**
* 實(shí)現(xiàn)
*
*/
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;
}
}
最后結(jié)果:
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. 裝飾器模式與其他相關(guān)模式
1)Adapter 模式:Decorator模式不同于Adapter模式,因?yàn)檠b飾僅改變對(duì)象的職責(zé)而
不改變它的接口;而適配器將給對(duì)象一個(gè)全新的接口。
2)Composite模式:可以將裝飾視為一個(gè)退化的、僅有一個(gè)組件的組
合。然而,裝飾僅給對(duì)象添加一些額外的職責(zé)—它的目的不在于對(duì)象聚集。
3)Strategy模式:用一個(gè)裝飾你可以改變對(duì)象的外表;而Strategy模
式使得你可以改變對(duì)象的內(nèi)核。這是改變對(duì)象的兩種途徑。
10.總結(jié)
1)使用裝飾器設(shè)計(jì)模式設(shè)計(jì)類(lèi)的目標(biāo)是: 不必重寫(xiě)任何已有的功能性代碼,而是對(duì)某個(gè)基于對(duì)象應(yīng)用增量變化。
2) 裝飾器設(shè)計(jì)模式采用這樣的構(gòu)建方式: 在主代碼流中應(yīng)該能夠直接插入一個(gè)或多個(gè)更改或“裝飾”目標(biāo)對(duì)象的裝飾器,
同時(shí)不影響其他代碼流。
3) Decorator模式采用對(duì)象組合而非繼承的手法,實(shí)現(xiàn)了在運(yùn)行時(shí)動(dòng)態(tài)的擴(kuò)展對(duì)象功能的能力,
而且可以根據(jù)需要擴(kuò)展多個(gè)功能,避免了單獨(dú)使用繼承帶來(lái)的“靈活性差”和“多子類(lèi)衍生問(wèn)題”。
同時(shí)它很好地符合面向?qū)ο笤O(shè)計(jì)原則中“優(yōu)先使用對(duì)象組合而非繼承”和“開(kāi)放-封閉”原則。
也許裝飾器模式最重要的一個(gè)方面是它的超過(guò)繼承的能力?!皢?wèn)題”部分展現(xiàn)了一個(gè)使用繼承的子類(lèi)爆炸。
基于裝飾器模式的解決方案,UML類(lèi)圖展現(xiàn)了這個(gè)簡(jiǎn)潔靈活的解決方案。
更多關(guān)于PHP相關(guān)內(nèi)容感興趣的讀者可查看本站專(zhuān)題:《php面向?qū)ο蟪绦蛟O(shè)計(jì)入門(mén)教程》、《PHP數(shù)組(Array)操作技巧大全》、《PHP基本語(yǔ)法入門(mén)教程》、《PHP運(yùn)算與運(yùn)算符用法總結(jié)》、《php字符串(string)用法總結(jié)》、《php+mysql數(shù)據(jù)庫(kù)操作入門(mén)教程》及《php常見(jiàn)數(shù)據(jù)庫(kù)操作技巧匯總》
希望本文所述對(duì)大家PHP程序設(shè)計(jì)有所幫助。
您可能感興趣的文章:- PHP設(shè)計(jì)模式(九)外觀模式Facade實(shí)例詳解【結(jié)構(gòu)型】
- PHP設(shè)計(jì)模式(七)組合模式Composite實(shí)例詳解【結(jié)構(gòu)型】
- PHP設(shè)計(jì)模式(六)橋連模式Bridge實(shí)例詳解【結(jié)構(gòu)型】
- PHP設(shè)計(jì)模式(五)適配器模式Adapter實(shí)例詳解【結(jié)構(gòu)型】
- PHP設(shè)計(jì)模式(四)原型模式Prototype實(shí)例詳解【創(chuàng)建型】
- PHP設(shè)計(jì)模式(三)建造者模式Builder實(shí)例詳解【創(chuàng)建型】
- PHP設(shè)計(jì)模式(一)工廠模式Factory實(shí)例詳解【創(chuàng)建型】
- 深入分析PHP設(shè)計(jì)模式