decorator pattern là mẫu thiết kế để thêm chức năng vào các lớp hiện có mà không thay đổi các lớp hiện có. Thay vào đó, một lớp trang trí kết thúc tốt đẹp quanh một lớp khác và thường hiển thị cùng một giao diện với lớp được trang trí.
dụ cơ bản:
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>
Bây giờ, bạn có thể bị cám dỗ để nghĩ rằng bởi vì Zend_Form_Decorator_*
lớp học trang trí, và có một phương pháp render
, điều này tự động có nghĩa là đầu ra của lớp trang trí render
phương pháp sẽ luôn được trang trí với nội dung bổ sung bởi người trang trí. Nhưng về thanh tra của các ví dụ cơ bản của chúng tôi ở trên, chúng ta có thể dễ dàng nhận thấy điều này không nhất thiết phải là trường hợp ở tất cả tất nhiên, được minh họa bằng bổ sung (mặc dù khá vô dụng) ví dụ này:
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>
Đây là trong thực tế về cơ bản, có bao nhiêu công cụ trang trí Zend_Form_Decorator_*
hoạt động, nếu nó có ý nghĩa đối với họ để có chức năng này.
Đối với những người trang trí hợp lý, bạn có thể kiểm soát vị trí với ví dụ setOption('placement', 'append')
hoặc bằng cách chuyển tùy chọn 'placement' => 'append'
vào mảng tùy chọn.
Đối Zend_Form_Decorator_PrepareElements
, ví dụ, tùy chọn vị trí này là vô ích và do bỏ qua, vì nó chuẩn bị các yếu tố hình thức được sử dụng bởi một decorator ViewScript
, làm cho nó một trong những trang trí mà không chạm vào nội dung trả lại của các yếu tố trang trí .
Tùy thuộc vào chức năng mặc định của các trang trí riêng lẻ, nội dung của lớp được trang trí, được thêm vào, được thêm vào, loại bỏ hoặc điều gì đó hoàn toàn khác với lớp được trang trí mà không thêm nội dung nào trực tiếp vào nội dung, trước khi chuyển nội dung sang trang trí tiếp theo. Hãy xem xét ví dụ đơn giản này:
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>
Bây giờ, khi bạn thiết lập các trang trí cho một yếu tố Zend_Form_Element_*
, họ sẽ được bao bọc, và do đó thực hiện, theo thứ tự mà chúng được thêm vào.Vì vậy, đi bằng ví dụ của bạn:
$decorate = array(
array('ViewHelper'),
array('Description'),
array('Errors', array('class'=>'error')),
array('Label', array('tag'=>'div', 'separator'=>' ')),
array('HtmlTag', array('tag' => 'li', 'class'=>'element')),
);
... cơ bản những gì xảy ra là như sau (tên lớp thực tế cắt ngắn cho ngắn gọn):
$decorator = new HtmlTag(new Label(new Errors(new Description(new ViewHelper()))));
echo $decorator->render();
Vì vậy, về kiểm tra đầu ra ví dụ của bạn, chúng ta nên có thể chưng cất hành vi vị trí mặc định của các trang trí riêng lẻ:
// 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>
Và bạn biết gì; điều này thực sự là là vị trí mặc định của tất cả các trang trí tương ứng.
Nhưng bây giờ đến phần khó khăn, chúng ta cần làm gì để có được kết quả mà bạn đang tìm kiếm? Để quấn label
và input
chúng ta không thể chỉ đơn giản là làm điều này:
$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')),
);
... vì điều này sẽ quấn tất cả nội dung trước (ViewHelper
, Description
, Errors
và Label
) với một div, phải không? Thậm chí không ... trang trí thêm sẽ được thay thế bằng trang trí tiếp theo, vì trang trí được thay thế bởi một trang trí sau nếu nó thuộc cùng một lớp. Thay vì bạn sẽ phải cung cấp cho nó một khóa duy nhất:
$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')),
);
Bây giờ, chúng tôi vẫn đang phải đối mặt với các vấn đề mà divWrapper
sẽ quấn tất cả nội dung trước (ViewHelper
, Description
, Errors
và Label
). Vì vậy, chúng ta cần phải sáng tạo ở đây. Có rất nhiều cách để đạt được những gì chúng ta muốn. Tôi sẽ đưa cho một ví dụ, đó có lẽ là dễ nhất:
$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
);
Đối với lời giải thích thêm về Zend_Form
trang trí tôi khuyên bạn nên đọc article about Zend Form Decorators
Chà, đó là một số câu trả lời hoàn chỉnh! Đẹp nhất! –
Oh wow, tôi đã có một khoảnh khắc ahaa trong câu trả lời đó, Cảm ơn và đã chấp nhận: D –
P.S đó là câu trả lời tuyệt vời nhất mà tôi từng có –