decorator pattern是一種設計模式,用於在不改變現有類的情況下向現有類添加功能。相反,裝飾類將自身封裝在另一個類中,並且通常將與裝飾類相同的接口公開。
基本例如:
interface Renderable
{
public function render();
}
class HelloWorld
implements Renderable
{
public function render()
{
return 'Hello world!';
}
}
class BoldDecorator
implements Renderable
{
protected $_decoratee;
public function __construct(Renderable $decoratee)
{
$this->_decoratee = $decoratee;
}
public function render()
{
return '<b>' . $this->_decoratee->render() . '</b>';
}
}
// wrapping (decorating) HelloWorld in a BoldDecorator
$decorator = new BoldDecorator(new HelloWorld());
echo $decorator->render();
// will output
<b>Hello world!</b>
現在,你可能會認爲,由於Zend_Form_Decorator_*
類是裝飾,並有render
方法,該裝置自動裝飾類render
法輸出將始終由裝飾者用額外的內容包裝。但在我們上面的基本範例的考察,我們可以很容易地看到,這並不一定是在所有課程的情況下,通過這種額外的(雖然沒什麼用)例所示:
class DivDecorator
implements Renderable
{
const PREPEND = 'prepend';
const APPEND = 'append';
const WRAP = 'wrap';
protected $_placement;
protected $_decoratee;
public function __construct(Renderable $decoratee, $placement = self::WRAP)
{
$this->_decoratee = $decoratee;
$this->_placement = $placement;
}
public function render()
{
$content = $this->_decoratee->render();
switch($this->_placement)
{
case self::PREPEND:
$content = '<div></div>' . $content;
break;
case self::APPEND:
$content = $content . '<div></div>';
break;
case self::WRAP:
default:
$content = '<div>' . $content . '</div>';
}
return $content;
}
}
// wrapping (decorating) HelloWorld in a BoldDecorator and a DivDecorator (with DivDecorator::APPEND)
$decorator = new DivDecorator(new BoldDecorator(new HelloWorld()), DivDecorator::APPEND);
echo $decorator->render();
// will output
<b>Hello world!</b><div></div>
這是事實上基本上裝飾者的工作方式很多,如果他們有這個安置功能是有道理的。
對於有意義的修飾器,例如,您可以使用setOption('placement', 'append')
來控制位置,或者通過將選項'placement' => 'append'
傳遞給選項數組。
對於Zend_Form_Decorator_PrepareElements
,例如,此放置選項是無用的,爲此忽略,因爲它準備由ViewScript
裝飾中使用的形式的元件,使得它不接觸的裝飾元素的呈現內容裝飾器的一個。
根據各個裝飾器的默認功能,裝飾類的內容被包裝,追加,前置,丟棄或對裝飾類完全不同的東西,不需要直接添加內容,然後將內容傳遞給下一個裝飾器。考慮一個簡單的例子:
class ErrorClassDecorator
implements Renderable
{
protected $_decoratee;
public function __construct(Renderable $decoratee)
{
$this->_decoratee = $decoratee;
}
public function render()
{
// imagine the following two fictional methods
if($this->_decoratee->hasErrors())
{
$this->_decoratee->setAttribute('class', 'errors');
}
// we didn't touch the rendered content, we just set the css class to 'errors' above
return $this->_decoratee->render();
}
}
// wrapping (decorating) HelloWorld in a BoldDecorator and an ErrorClassDecorator
$decorator = new ErrorClassDecorator(new BoldDecorator(new HelloWorld()));
echo $decorator->render();
// might output something like
<b class="errors">Hello world!</b>
現在,當您設置的裝飾爲Zend_Form_Element_*
元素,它們將被包裝,因此執行,在它們被添加的順序。所以,去你的榜樣:
$decorate = array(
array('ViewHelper'),
array('Description'),
array('Errors', array('class'=>'error')),
array('Label', array('tag'=>'div', 'separator'=>' ')),
array('HtmlTag', array('tag' => 'li', 'class'=>'element')),
);
...基本上會發生什麼是以下(實際的類名截斷簡潔):
$decorator = new HtmlTag(new Label(new Errors(new Description(new ViewHelper()))));
echo $decorator->render();
因此,在檢查你的榜樣輸出,我們應該能夠提取各個裝飾者的默認放置行爲:
// ViewHelper->render()
<input type="text" name="title" id="title" value="">
// Description->render()
<input type="text" name="title" id="title" value="">
<p class="hint">No --- way</p> // placement: append
// Errors->render()
<input type="text" name="title" id="title" value="">
<p class="hint">No --- way</p>
<ul class="error"> // placement: append
<li>Value is required and cant be empty</li>
</ul>
// Label->render()
<label for="title" class="required">Title</label> // placement: prepend
<input type="text" name="title" id="title" value="">
<p class="hint">No --- way</p>
<ul class="error">
<li>Value is required and cant be empty</li>
</ul>
// HtmlTag->render()
<li class="element"> // placement: wrap
<label for="title" class="required">Title</label>
<input type="text" name="title" id="title" value="">
<p class="hint">No --- way</p>
<ul class="error">
<li>Value is required and cant be empty</li>
</ul>
</li>
你知道什麼;這實際上是是所有各個裝飾器的默認放置。
但是現在出現了困難的部分,我們需要做些什麼來獲得您要查找的結果?爲了包裹label
和input
,我們不能簡單地這樣做:
$decorate = array(
array('ViewHelper'),
array('Description'),
array('Errors', array('class'=>'error')),
array('Label', array('tag'=>'div', 'separator'=>' ')),
array('HtmlTag', array('tag' => 'div')), // default placement: wrap
array('HtmlTag', array('tag' => 'li', 'class'=>'element')),
);
...因爲這將包裝所有前面的內容(ViewHelper
,Description
,Errors
和Label
)有一個div,對不對?甚至沒有...添加的裝飾器將被替換爲下一個裝飾器,因爲如果它是同一個類,裝飾器將被替換爲下面的裝飾器。在代替你必須給它一個唯一的密鑰:
$decorate = array(
array('ViewHelper'),
array('Description'),
array('Errors', array('class'=>'error')),
array('Label', array('tag'=>'div', 'separator'=>' ')),
array(array('divWrapper' => 'HtmlTag'), array('tag' => 'div')), // we'll call it divWrapper
array('HtmlTag', array('tag' => 'li', 'class'=>'element')),
);
現在,我們仍然面臨着divWrapper
將包裝所有前面的內容(ViewHelper
,Description
,Errors
和Label
)的問題。所以我們需要在這裏創意。有很多方法可以實現我們想要的。我舉一個例子,這可能是最簡單的:
$decorate = array(
array('ViewHelper'),
array('Label', array('tag'=>'div', 'separator'=>' ')), // default placement: prepend
array(array('divWrapper' => 'HtmlTag'), array('tag' => 'div')), // default placement: wrap
array('Description'), // default placement: append
array('Errors', array('class'=>'error')), // default placement: append
array('HtmlTag', array('tag' => 'li', 'class'=>'element')), // default placement: wrap
);
有關Zend_Form
裝飾我建議你閱讀Zend框架的首席開發人員馬修·威爾O'Phinney的article about Zend Form Decorators
哇,這是一些完整的答案!好一個 ! –
哦哇,我有一個答案在那個答案,謝謝並接受:D –
P.S這是最有趣的回答有史以來有 –