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这是最有趣的回答有史以来有 –