Introduction

Decorator Basics 装饰器基础

Overview of the Decorator Pattern 装饰器模式概述

To begin, we'll cover some background on the » Decorator design pattern. One common technique is to define a common interface that both your originating object and decorator will implement; your decorator than accepts the originating object as a dependency, and will either proxy to it or override its methods. Let's put that into code to make it more easily understood:

首先,我们将介绍一些装饰器设计模式的背景知识。一个常见的技术是定义一个通用接口,你的原始对象和装饰器都会实现这个接口。你的装饰器除了接收原始对象作为一个依赖之外,还会代理或覆盖它的方法。咱们使用代码来说明以便理解。

  1. interface Window
  2. {
  3.     public function isOpen();
  4.     public function open();
  5.     public function close();
  6. }
  7.  
  8. class StandardWindow implements Window
  9. {
  10.     protected $_open = false;
  11.  
  12.     public function isOpen()
  13.     {
  14.         return $this->_open;
  15.     }
  16.  
  17.     public function open()
  18.     {
  19.         if (!$this->_open) {
  20.             $this->_open = true;
  21.         }
  22.     }
  23.  
  24.     public function close()
  25.     {
  26.         if ($this->_open) {
  27.             $this->_open = false;
  28.         }
  29.     }
  30. }
  31.  
  32. class LockedWindow implements Window
  33. {
  34.     protected $_window;
  35.  
  36.     public function __construct(Window $window)
  37.     {
  38.         $this->_window = $window;
  39.         $this->_window->close();
  40.     }
  41.  
  42.     public function isOpen()
  43.     {
  44.         return false;
  45.     }
  46.  
  47.     public function open()
  48.     {
  49.         throw new Exception('Cannot open locked windows');
  50.     }
  51.  
  52.     public function close()
  53.     {
  54.         $this->_window->close();
  55.     }
  56. }

You then create an object of type StandardWindow, pass it to the constructor of LockedWindow, and your window instance now has different behavior. The beauty is that you don't have to implement any sort of "locking" functionality on your standard window class -- the decorator takes care of that for you. In the meantime, you can pass your locked window around as if it were just another window.

你首先创建了一个StandardWindow类型的对象,然后将它传给了LockedWindow的构造函数,这样你的实例对象就获得了不同的行为表现。 优雅之处就在于你无需在StandardWindow类中实现任何locking的功能,装饰器替你实现了。同时,你可以把你的locked window像一个其它的window对象一样传出去。

One particular place where the decorator pattern is useful is for creating textual representations of objects. As an example, you might have a "Person" object that, by itself, has no textual representation. By using the Decorator pattern, you can create an object that will act as if it were a Person, but also provide the ability to render that Person textually.

装饰器模式一个有用的场合是为对象创建toString()的时候。例如,你有一个Person对象,他本身没有toString()。通过使用装饰器模式,你可以创建一个对象,不仅行为与Person 相同,同时还提供了toString()的能力。

In this particular example, we're going to use » duck typing instead of an explicit interface. This allows our implementation to be a bit more flexible, while still allowing the decorator object to act exactly as if it were a Person object.

在本例中,我们将继续使用duck类型,而不是显式接口类型。这允许我们的实现可以更加灵活,同时还允许装饰器对象可以拥有Person对象一样的行为。

  1. class Person
  2. {
  3.     public function setFirstName($name) {}
  4.     public function getFirstName() {}
  5.     public function setLastName($name) {}
  6.     public function getLastName() {}
  7.     public function setTitle($title) {}
  8.     public function getTitle() {}
  9. }
  10.  
  11. class TextPerson
  12. {
  13.     protected $_person;
  14.  
  15.     public function __construct(Person $person)
  16.     {
  17.         $this->_person = $person;
  18.     }
  19.  
  20.     public function __call($method, $args)
  21.     {
  22.         if (!method_exists($this->_person, $method)) {
  23.             throw new Exception('Invalid method called on HtmlPerson: '
  24.                 .  $method);
  25.         }
  26.         return call_user_func_array(array($this->_person, $method), $args);
  27.     }
  28.  
  29.     public function __toString()
  30.     {
  31.         return $this->_person->getTitle() . ' '
  32.             . $this->_person->getFirstName() . ' '
  33.             . $this->_person->getLastName();
  34.     }
  35. }

In this example, you pass your Person instance to the TextPerson constructor. By using method overloading, you are able to continue to call all the methods of Person -- to set the first name, last name, or title -- but you also now gain a string representation via the __toString() method.

本例中,你传递一个Person对象给TextPerson的构造函数。通过使用方法重载,你可以继续调用Person本身的所有方法(如setFirstName、getFirstName等), 但你还获得了一个toString方法。

This latter example is getting close to how Zend_Form decorators work. The key difference is that instead of a decorator wrapping the element, the element has one or more decorators attached to it that it then injects itself into in order to render. The decorator then can access the element's methods and properties in order to create a representation of the element -- or a subset of it.

这个例子与Zend_Form装饰器的工作原理更接近。关键区别在于,不是通过装饰器去包裹表单元素, 而是将一个或多个装饰器对象附加于表单元素对象,然后将元素对象自身注入这些装饰器, 这样装饰器就可以访问元素对象的方法和属性。

Creating Your First Decorator 创建你的第一个装饰器

Zend_Form decorators all implement a common interface, Zend_Form_Decorator_Interface. That interface provides the ability to set decorator-specific options, register and retrieve the element, and render. A base decorator, Zend_Form_Decorator_Abstract, provides most of the functionality you will ever need, with the exception of the rendering logic.

所有的Zend Form装饰器都实现了一个通用接口Zend_Form_Decorator_Interface。 这个接口提供了包括设置装饰器特定选项、注册表单元素、取得表单元素、呈现等的功能。 一个基本的装饰器Zend_Form_Decorator_Abstract,提供了你需要的大部分功能,还有呈现逻辑中可能出现的异常。

Let's consider a situation where we simply want to render an element as a standard form text input with a label. We won't worry about error handling or whether or not the element should be wrapped within other tags for now -- just the basics. Such a decorator might look like this:

我们来考虑一个场合,比如我们希望呈现一个带有label的标准文本框元素。我们先不考虑错误处理或者是否这个元素应该包裹于其它的html标签,仅仅考虑最简单的情况。 这样一个装饰器应该如下所示。

  1. class My_Decorator_SimpleInput extends Zend_Form_Decorator_Abstract
  2. {
  3.     protected $_format = '<label for="%s">%s</label>'
  4.                        . '<input id="%s" name="%s" type="text" value="%s"/>';
  5.  
  6.     public function render($content)
  7.     {
  8.         $element = $this->getElement();
  9.         $name    = htmlentities($element->getFullyQualifiedName());
  10.         $label   = htmlentities($element->getLabel());
  11.         $id      = htmlentities($element->getId());
  12.         $value   = htmlentities($element->getValue());
  13.  
  14.         $markup  = sprintf($this->_format, $name, $label, $id, $name, $value);
  15.         return $markup;
  16.     }
  17. }

Let's create an element that uses this decorator:

下面我们创建一个使用这个装饰器的元素。

  1. $decorator = new My_Decorator_SimpleInput();
  2. $element   = new Zend_Form_Element('foo', array(
  3.     'label'      => 'Foo',
  4.     'belongsTo'  => 'bar',
  5.     'value'      => 'test',
  6.     'decorators' => array($decorator),
  7. ));

Rendering this element results in the following markup:

这个元素呈现出的html应该如下。

  1. <label for="bar[foo]">Foo</label>
  2. <input id="bar-foo" name="bar[foo]" type="text" value="test"/>

You could also put this class in your library somewhere, inform your element of that path, and refer to the decorator as simply "SimpleInput" as well:

你也可以将这个装饰器类放在你的类库中,把路径告诉你的元素,同样可以通过SimpleInput来引用它。

  1. $element = new Zend_Form_Element('foo', array(
  2.     'label'      => 'Foo',
  3.     'belongsTo'  => 'bar',
  4.     'value'      => 'test',
  5.     'prefixPath' => array('decorator' => array(
  6.         'My_Decorator' => 'path/to/decorators/',
  7.     )),
  8.     'decorators' => array('SimpleInput'),
  9. ));

This gives you the benefit of re-use in other projects, and also opens the door for providing alternate implementations of that decorator later.

这样你就可以实现这个类在其它项目中的复用,还可以为其提供不同的实现。

In the next section, we'll look at how to combine decorators in order to create composite output.

在下一节中,我们将会看一下如何组合装饰器以获得组合式的输出。


Introduction