Patterns For PHP
Index Patterns News Forums

Decorator

From Patterns For PHP

Contents

Introduction

Note: This is a draft article dated 14 September 2006. The supplied code has not been tested.

Altering the behaviour of objects is a tricky business. In many cases you do not wish to alter the original object but rather tack on additional or modified behaviours. The easiest way to achieve this is using inheritance. Creating a series of subclasses allows a programmer to add additional methods and properties or alter those which already exist in the parent class. Unfortunately, as other Design Patterns often point out, inheritance can be a path full of pitfalls and traps for the unwary. The more layers exist in a class hierarchy, the more cumbersome and difficult to manage the whole structure becomes.

If we decide to set a goal of maintaining the original class as is and of disallowing any further inheritance, we immediately gain some measure of control. By preventing further inheritance we have a smaller discrete unit perfect for reuse in other projects minus the specific modifications current circumstances might require. To extend the original class and add new or modified behaviour to it we can use the Decorator Pattern.

The Decorator Pattern creates a separate class family which adds extended behaviour to an existing class while leaving the existing class unchanged. As the class is ported for reuse, we can transparently add new Decorators specific to each new application as we see fit. Additionally, since the Decorator classes are in a separate hierarchy they have a variable type which is distinct from the original class, i.e. we can tell if a class is a Decorator.

Of course the Decorator Pattern is not a silver bullet. Inheritance remains the simplest option and the Decorator Pattern is not suitable for all scenarios. However it remains an extremely useful option and is one of the more popular Design Patterns programmers are intimately familiar with, especially in web application development.

Pattern Definition

Definition: The Decorator Pattern represents the process of creating a new class hierarchy which adds new or modified behaviour to an existing class without modifying the existing class from its original state.

Description of the Problem

Let's introduce an example of a situation which lends itself to improvement through the application of the Design Pattern. During the development of an application we discover a need to store data to a file. For simplicity, the data is a simple string. A generic String_Writer class is ported from a pre-existing project. The class simply accepts a string and writes it to a specified file, storing the data in a property access by a public getter/setter pair. The class is generic in that it just stores strings. Outputting and additional formatting for display is not handled by the basic class. As a result it is decided to extend the String_Writer class behaviour to add formatting options for output and to add an output method.

Inheritance has been ruled out as an option for a number of reasons. First of all, there can be any number of formatting options. Adding each option to the original String_Writer class through inheritance could become complex, resulting in a class hierarchy many levels deep. Changes to any class high in the hierarchy might require modifying all subsequent child classes for the change in interface or properties in the higher level parent - a time-consuming task.

The Decorator Pattern on the other hand avoids multi-level inheritance.

Implementing the Decorator Pattern in PHP5

The basic String_Writer class is extremely simple:

php
class String_Writer {
 
    private $path = '';
    private $string = '';
 
    public function __construct($path) {
        $this->path = $path;
    }
 
    public function setString($string) {
        $this->string = $string;
    }
 
    public function getString() {
        return $this->string;
    }
 
    public function getPath() {
        return $this->path;
    }
 
    public function store() {
        // we assume one string per line
        if(!file_put_contents($this->path, $this->string . PHP_EOL, FILE_APPEND))
        {
            throw new Exception('Error writing to specified path');
        }
    }
    
}

The String_Writer class should be fairly simple to follow. We instantiate the class passing the path to the relevant log file in the constructor parameter. To store a new string in the file, we call setString(), followed by store(). Each store appends a newline to the data using the PHP_EOL constant to be agreeable for Unix, Mac or Windows systems.

In order to format the stored string for output without resorting to inheritance, we create a new class hierarchy. It's structure will be simple, a parent class and any number of specific child classes. We will call the parent class String_Formatter. The Formatter and its children implement the Decorator Pattern, i.e. they extend the functionality of the existing class, String_Writer, by supplying new formatting and output options. The String_Writer class is left unchanged.

The String_Formatter class is as follows:

php
class String_Formatter {
 
    private $string = '';
 
    public function __construct(String_Writer $writer) {
        $this->string = $writer->getString();
    }
 
    public function getString() {
        return $this->string;
    }
 
    public function setString($string) {
        $this->string = $string;
    }
 
    public function output() {
        return $this->string;
    }
 
}

The String_Formatter class is likewise simple. The additional behaviour is simply the output() method. By default this simply returns the value of the string held by the String_Writer object. No formatting is performed by default.

To make things interesting, we extend our basic String_Formatter class with a selection of child classes which format the output string. Here's three examples: Uppercase, Reverse, and Escape.

php
interface iString_Formatter {
 
    public function format();
 
}
 
class String_Formatter_Uppercase extends String_Formatter implements iString_Formatter {
 
    private $stringFormatter;
 
    public function __construct(String_Formatter $stringFormatter) {
        $this->stringFormatter = $stringFormatter;
    }
 
    public function format() {
        $this->stringFormatter->setString(
            strtoupper($this->stringFormatter->getString())
        );
    }
 
}
 
class String_Formatter_Reverse extends String_Formatter implements iString_Formatter {
 
    private $stringFormatter;
 
    public function __construct(String_Formatter $stringFormatter) {
        $this->stringFormatter = $stringFormatter;
    }
 
    public function format() {
        $this->stringFormatter->setString(
            strrev($this->stringFormatter->getString()));
    }
 
}
 
class String_Formatter_Escape extends String_Formatter implements iString_Formatter {
 
    private $stringFormatter;
 
    public function __construct(String_Formatter $stringFormatter) {
        $this->stringFormatter = $stringFormatter;
    }
 
    public function format() {
        $this->stringFormatter->setString(
            htmlentities($this->stringFormatter->getString(), ENT_QUOTES, 'UTF-8')
        );
    }
 
}

Each individual subclass performs a different formatting function. Of course using just one type of formatting is limited. Rather each subclass of String_Formatter accepts other related classes of the same type as arguments to its constructor. By linking all the decorators to each other, it is possible to perform specific formatting by calling each Decorator's formatting method.

An example usage:

php
require('String_Writer.php');
require('String_Formatter.php');
require('iString_Formatter.php');
require('String_Formatter_Uppercase.php');
require('String_Formatter_Reverse.php');
require('String_Formatter_Escape.php');
 
$string = 'php rof snrettap';
$path = '/tmp/stringlog';
 
/*
Create a new String_Writer object
*/
$stringWriter = new String_Writer($path);
$stringWriter->setString($string);
 
/*
Store the string to a file
*/
$stringWriter->store();
 
/*
Instantiate a String_Formatter object to control output
*/
$stringFormatter = new String_Formatter($stringWriter);
 
/*
Instantiate specific String_Formatters. Pass in String_Formatter object
as parameter to first Formatter. Pass new Formatter into third, etc.
This chained set of parameters links all the Formatters to the same string.
*/
$stringUppercase = new String_Formatter_Uppercase($stringFormatter);
$stringReverse = new String_Formatter_Reverse($stringUppercase);
$stringEscape = new String_Formatter_Escape($stringReverse);
 
/*
Perform the formatting, call format() method of each required Formatting option
*/
$stringUppercase->format();
$stringReverse->format();
$stringEscape->format();
 
/*
Return the results for output
*/
$output = $stringFormatter->output();
 
/*
Exit the script while printing the resulting formatted string (which is also
stored in our log file in original form)
*/
exit($output); // PATTERNS FOR PHP (reversed, uppercased and escaped!) 

Disadvantages

The Decorator Pattern is not suitable for all possible situations. If the number of Decorator options is limited and it does not add significantly to the number of hierarchy levels then inheritance is possibly a better option. In general however, class hierarchies should be fairly shallow. If a hierachy of classes is more than three levels deep then you should consider refactoring some of the additional elements using the Decorator Pattern or certain other Patterns instead. A deep class hierarchy can easily become a morass of dependencies where changes have knock on effects across numerous inheriting classes. Avoiding such maintenance black holes should be a priority.

Conclusion

In this article we have briefly examined a simple implementation of the Decorator Pattern. This pattern is an alternative to persistent subclassing in order to add new or modified behaviour to the ultimate parent class. Rather than using inheritance, one can build a separate class hierarchy of the behaviour required and apply the methods of the new classes to the original class. An essential feature of the Decorator Pattern is that the original class does not require any additional modification in order to apply the Pattern.

Retrieved from "/wiki/Decorator"
MediaWiki
GNU Free Documentation License 1.2