Composite Pattern

The pattern allows:

  • to make an architecture where single object instances and collections of these objects are treated uniformly.

Composite Pattern


When dealing with tree-structured data difference between leaf and branch interfaces makes the code more complex and can cause errors. The pattern is meant to solve the problem.


PHP Example

In this example the objects Article (leaf) and Content_Composite (branch) have the same interface. An instance of each has the method save. Any other methods can be added there when necessary. Your client object (e.g. a model) is not required to know who to treat the exact object instance it traverses – they all treated the same way.


<?php
/*
 * @category Design Pattern Tutorial
 * @package Composite Sample
 * @author Dmitry Sheiko <me@dsheiko.com>
 * @link http://dsheiko.com
 */
/**
 * Component
 */
interface Content_Interface
{
    public function save();
}
/**
 * Composite
 */
class Content_Composite implements Content_Interface
{
    private $_items = array();

    public function addItem(Content_Interface $content)
    {
        $this->_items[spl_object_hash($content)] = $content;
    }
    public function removeItem(Content_Interface $content)
    {
        unset($this->_items[spl_object_hash($content)]);
    }
    public function save()
    {
        foreach ($this->_items as $content) {
            $content->save();
        }
    }
}
/**
 * Leaf
 */
class Article implements Content_Interface
{
    public $id;
    public $title;

    public function  __construct($title)
    {
        static $_id = 1;
        $this->id = $_id ++;
        $this->title = $title;
    }
    public function save()
    {
        printf ("Article #%d, %s saved\n", $this->id, $this->title);
    }
}

/**
 * Usage
 */
$article1 = new Article('Title 1');
$article2 = new Article('Title 2');
$article3 = new Article('Title 3');
$composite1 = new Content_Composite();
$composite11 = new Content_Composite();
$composite12 = new Content_Composite();
$composite11->addItem($article1);
$composite11->addItem($article2);
$composite12->addItem($article3);
$composite1->addItem($composite11);
$composite1->addItem($composite12);
$composite1->save();

// Output:
// Article #1, Title 1 saved
// Article #2, Title 2 saved
// Article #3, Title 3 saved

JS/ES5 Example

/*
 * @category Design Pattern Tutorial
 * @package Composite Sample
 * @author Dmitry Sheiko <me@dsheiko.com>
 * @link http://dsheiko.com
 */
(function() {
/**
 * Composite
 */
var ContentComposite = function() {
    var _items = [];
    return {
        addItem: function(content) {
            _items.push(content);
        },
        save: function() {
            for (var i in _items) {
                _items[i].save();
            }
        }

    }
};
/**
 * Leaf
 */
var Article = function(title) {
    Article._id = Article._id || 1;
    this.id = Article._id ++;
    this.title = title;
    this.save = function() {
        console.log("Article #" + this.id + ", " + this.title + " saved");
    }
};

/**
 * Usage
 */
var article1 = new Article('Title 1');
var article2 = new Article('Title 2');
var article3 = new Article('Title 3');
var composite1 = new ContentComposite();
var composite11 = new ContentComposite();
var composite12 = new ContentComposite();

composite11.addItem(article1);
composite11.addItem(article2);
composite12.addItem(article3);
composite1.addItem(composite11);
composite1.addItem(composite12);
composite1.save();

// Output:
// Article #1, Title 1 saved
// Article #2, Title 2 saved
// Article #3, Title 3 saved

}());