Decorator Pattern

The pattern allows:

  • for extending (decoration) the functionality of a certain object at run-time, independently of other instances of the same class.

Decorator Pattern


PHP Example

Being a web-developer, definitely, you have been having more than once the requirements to prepare user generated content view before showing it. I mean stripping XSS, breaking long strings or maybe parsing template language (e.g. BBcode). So you have a content object and need to decorate it before display. So the content can be given to Decorator to make all required checks and changes.

<?php
/*
 * @category Design Pattern Tutorial
 * @package Decorator Sample
 * @author Dmitry Sheiko <me@dsheiko.com>
 * @link http://dsheiko.com
 */

class ContentRowArray extends ArrayObject
{
}

class Dao_Comment
{
    public function fetchAll()
    {
        return new ContentRowArray(array(
            array("title" => 'Lorem [b]ipsum[/b]', 'body' => 'Lorem ipsum'),
            array("title" => 'Lorem ipsum', 'body' => 'Lorem <script>alert(1);</script>ipsum')
        ));
    }
}

class ContentRowListDecorator
{
    private $_rowArray;
    public function  __construct(ContentRowArray &$rowArray)
    {
        $this->_rowArray = &$rowArray;
    }
    public function parseBBCode()
    {
        $this->_rowArray = new ContentRowArray(array_map(function ($li) {
            // do parsing
            return preg_replace("/\[b\](.*?)\[\/b\]/is", "<b>\\1</b>", $li); }
            , $this->_rowArray->getArrayCopy()));
    }
    public function breakLongStrings()
    {
        //..
    }
    public function stripXss()
    {
        $this->_rowArray = new ContentRowArray(array_map(function ($li) {
            // do stripping
            return preg_replace("/(<script.*?>.*?<\/script>)/", "", $li); }
            , $this->_rowArray->getArrayCopy()));
    }
}

/**
 * Usage
 */
$comment = new Dao_Comment();
$rowArray = $comment->fetchAll();
$decorator = new ContentRowListDecorator($rowArray);
$decorator->parseBBCode();
$decorator->stripXss();
print_r($rowArray);

// Output:
// ContentRowArray Object
// (
//    [storage:ArrayObject:private] => Array
//        (
//            [0] => Array
//                (
//                    [title] => Lorem <b>ipsum</b>
//                    [body] => Lorem ipsum
//                )
//
//            [1] => Array
//                (
//                    [title] => Lorem ipsum
//                    [body] => Lorem ipsum
//                )
//
//        )
//
// )

JS/ES5 Example

Here we have a shim implemented as a Decorator. Let’s say you don’t know in advance if client’s browser supports CSS instruction like: p:first-letter { text-transform: uppercase; }. So, in order to have all the first letters of text paragraphs capitalized, you can use a decorator on the text.

/*
 * @category Design Pattern Tutorial
 * @package Decorator Sample
 * @author Dmitry Sheiko <me@dsheiko.com>
 * @link http://dsheiko.com
 */
(function() {
var Widget = function() {
    return {
        list : ['Lorem ipsum', 'Lorem ipsum'],
        toString: function() {
            return this.list.reduce(function (accum, value) {
                return accum + "\n" + value;
            });
        }
    }
}

var CssShimDecorator = function(widget) {
    return {
        shimCaps : function() {
            widget.list.forEach(function (value, i, arr) {
                arr[i] += '<span>' + value.substr(0,1) + '</span>' + value.substr(1);
            });
        }
    }
}

/**
 * Usage
 */
var wg = new Widget();
console.log(wg.toString());
var shim = new CssShimDecorator(wg);
shim.shimCaps();
console.log(wg.toString());

// Output
// Lorem ipsum
// Lorem ipsum
// Lorem ipsum<span>L</span>orem ipsum
// Lorem ipsum<span>L</span>orem ipsum

}());