Today we will explore the Decorator pattern, an alternative to subclassing which favors object composition over class inheritance.
A common issue of subclassing is its rigidity: when different subclasses provide different features, it's hard to combine them into a unique object.
A classic example consists in considering a Ball class and two BouncingBall and ColoredBall subclasses. What should I instantiate if I want a ColoredBouncingBall? There are different solutions that fall in the subclassing realm:
- one more subclass is created; often multiple inheritance is not allowed and thus some code from BouncingBall or ColoredBall would be duplicated.
- If the desired ball traits are three or more, for instance because a RollingBall class exists, there would be a proliferation of subclasses: ColoredBouncingBall, ColoredRollingBall, BouncingRollingBall and ColoredBouncingRollingBall. If you add a business requirement (the client wants rolling balls) and the codebase explodes, this is a sign that something is wrong in the design: when two different balls are requested, we should take the first bullet and refactor into a Decorator pattern, where every other trait will require only one additional class instead of doubling the number of concrete classes.
- We can also prepare a God class that puts together the different Colored and Bouncing behaviors, but remember that a class should have only one responsibility. Why adding traits that sometimes will never be used to an object? This solution forces all Balls to be an instance of BouncingColoredRollingBorderedFlashingBall.
Their job is to delegate all the methods of the Component interface to the wrapped Component instance, and to decorate some of them with additional behavior. Since the wrapping and delegation code is common to all the ConcreteDecorators of the same Component, it is usually shared in a base class.
Since Decorators work on a Component and not on ConcreteComponents, they can compose other Decorators, showing their affinity to the Composite pattern. In the diagram, ConcreteDecorators (which are not shown) would be subclasses of Decorator, which keep the inheritance tree at only one level of depth.
Since Decorators conform to Component, the Client does not notice if a ConcreteComponent is substituted with a Decorator composing it, or with a Decorator composing a Decorator composing it.
The code sample is provided in a Before & After form, referring to the application of the pattern to a design problem. This new form is a useful suggestion from an anonymous reader.
The pattern implementation is similar to Zend Framework's Zend_Form component's decorators, but those kind of decorators are implementations of a "renderer" interface Zend_Form_Decorator and not of the entire element interface.
Before
<?php /** * Represents a <input type="text" /> html element. * It can be created programmatically and then printed. */ class InputText { protected $_name; public function __construct($name) { $this->_name = $name; } public function getName() { return $this->_name; } public function __toString() { return "<input type=\"text\" id=\"{$this->_name}\" name=\"{$this->_name}\" />\n"; } } /** * Adds a custom <label> element alongside the <input> one. */ class LabelledInputText extends InputText { protected $_label; public function setLabel($label) { $this->_label = $label; } public function __toString() { return "<label for=\"{$this->_name}\">{$this->_label}</label>\n" . parent::__toString(); } } /** * Adds a <span> containing an error message after the <input> element. */ class ErrorInputText extends InputText { protected $_error; public function setError($message) { $this->_error = $message; } public function __toString() { return parent::__toString() . "<span>{$this->_error}</span>\n"; } } $input = new LabelledInputText('nickname'); $input->setLabel('Nick:'); echo $input, "\n"; $input = new ErrorInputText('nickname'); $input->setError('You must enter a unique nickname'); echo $input; // but how can we obtain a LabelledErrorInputText, which has both the <label> // and <span> elements? The subclassing-based design has clear limitations.After
<?php /** * The smallest cohesive interface we can think of for this type * of Decorator. This is the Component interface. */ interface HtmlElement { /** * @return string html code */ public function __toString(); /** * @return string the name of the POST request key for this element, * aka the "name" attribute. */ public function getName(); } /** * Represents a <input type="text" /> html element. * It can be created programmatically and then printed. * This is the only ConcreteComponent. */ class InputText implements HtmlElement { protected $_name; public function __construct($name) { $this->_name = $name; } public function getName() { return $this->_name; } public function __toString() { return "<input type=\"text\" id=\"{$this->_name}\" name=\"{$this->_name}\" />\n"; } } /** * Very simple base class to share the wrapping code between Decorators. * This is the Decorator participant. */ abstract class HtmlDecorator implements HtmlElement { protected $_element; public function __construct(HtmlElement $input) { $this->_element = $input; } /** * All operations are delegated by default, not changing anything * of the original behavior. * ConcreteDecorators will override the methods they are interested in. */ public function getName() { return $this->_element->getName(); } public function __toString() { return $this->_element->__toString(); } } /** * Adds a custom <label> element alongside the <input> one. * Example of ConcreteDecorator. */ class LabelDecorator extends HtmlDecorator { protected $_label; public function setLabel($label) { $this->_label = $label; } public function __toString() { $name = $this->getName(); return "<label for=\"{$name}\">{$this->_label}</label>\n" . $this->_element->__toString(); } } /** * Adds a <span> containing an error message after the <input> element. * Example of ConcreteDecorator. */ class ErrorDecorator extends HtmlDecorator { protected $_error; public function setError($message) { $this->_error = $message; } public function __toString() { return $this->_element->__toString() . "<span>{$this->_error}</span>\n"; } } $input = new InputText('nickname'); $labelled = new LabelDecorator($input); $labelled->setLabel('Nick:'); echo $labelled, "\n"; $input = new InputText('nickname'); $error = new ErrorDecorator($input); $error->setError('You must enter a unique nickname'); echo $error, "\n"; // how can we obtain a LabelledErrorInputText, which has both the <label> // and <span> elements? $input = new InputText('nickname'); $labelled = new LabelDecorator($input); $labelled->setLabel('Nick:'); $error = new ErrorDecorator($labelled); // a Decorator wrapping another one $error->setError('You must enter a unique nickname'); echo $error;The resulting architecture of a Decorator pattern is a graph of the mandatory number of cohesive classes and small objects, which is highly preferable to a high number of classes and God objects. The final behavior is not obtained by choosing something to subclass but by instantiating different Decorators and linking them in a chain of objects, while maintaining the original Component abstraction all the time.
I hope you liked the "before and after the cure" style of this article. It may become the norm for the next posts of this series.
Before-After "pattern" is good :) What would be else useful is providing not only general UML diagram, but also "concrete" diagram for your example.
ReplyDeleteThere is one other way to extend object at run time or alter its behaviour, by using anonymous functions or closures (as of 5.3).
ReplyDeleteI've written a class to allow closures access to private and protected members of an object (posted on http://blog.wsoczynski.pl/articles/show/id/49)
I am not a big fan of mixing up object-oriented and functional programming mercilessly, but I see the point of your demonstration. :) However, a Decorator by definition should not alter the interface of an object, because the Client cannot distinguish between the original instance and the decorated one to take advantage of new methods (and it cannot distinguish because it does not care for decoupling reasons).
ReplyDeleteThis comment has been removed by the author.
ReplyDeleteWith traits in php5.4, doing this should now simply be easy. Using a list of traits, you should be able to make any number of bouncing, colored, flashing, rolling, flying, spinning, floating, fire-spitting, balls.
ReplyDeletehttp://www.php.net/manual/en/language.oop5.traits.php
Thanks for the series. Its great for people like me learning important patterns.
Main benefit of decorator is that it affect only individual object and not all object which itself a big control and flexibility inheritance doesn't offer. See here for another example of decorator pattern in Java.
ReplyDelete$input = new InputText('nickname');
ReplyDelete$labelled = new LabelDecorator($input);
$labelled->setLabel('Nick:');
$error = new ErrorDecorator($labelled); // a Decorator wrapping another one
$error->setError('You must enter a unique nickname');
echo $error;
I think this code is wrong because a constructor of HtmlDecorator just accept a object of HTMLElement,in this case you input HtmlDecorator will not be acceptable
Hoang,
ReplyDeleteHtmlDecorator implements HtmlElement, so it will be accepted by the constructor with a HtmlElement type hint. In general, Decorators implement *and* compose the interface they're wrapping so that they can be nested like you have written here.