Builder Pattern

The pattern allows:

  • decoupling construction of complex objects from the module;
  • multiple representations of object construction algorithm;
  • run-time control over object construction process;
  • an application design abstraction where one object acts as a Director and other as subordinated Builders.

Builder Pattern



PHP Example

Let us assume we need different page object representations for desktop and mobile site versions. To keep it easy, the only difference between the representations will be sidebar property. That one is required in desktop version and insufficient in mobile.

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

interface Director_Interface
{
    public function __construct(PageBuilder_Interface $builder);
}
interface PageBuilder_Interface
{
    public function build();
    public function getTitle();
    public function getBody();
    public function getSidebar();
}

class Page_Entity
{
    public $title;
    public $body;
    public $sidebar;
}
/**
 * Director
 */
class Page_Director implements Director_Interface
{
    private $_entity;
    public function  __construct(PageBuilder_Interface $builder)
    {
        $this->_entity = new Page_Entity();
        $this->_entity->title = $builder->getTitle();
        $this->_entity->body = $builder->getBody();
        $this->_entity->sidebar = $builder->getSidebar();
    }

    public function  __toString()
    {
        return json_encode($this->_entity);
    }
}
/**
 * Concrete builders
 */
class Page_DesktopBuilder implements PageBuilder_Interface
{
    private $_title = 'default value for desktop version';
    private $_body = 'default value for desktop version';
    private $_sidebar = 'default value for desktop version';

    public function setTitle($title)
    {
        $this->_title = $title;
        return $this;
    }
    public function setBody($body)
    {
        $this->_body = $body;
        return $this;
    }
    public function setSidebar($sidebar)
    {
        $this->_sidebar = $sidebar;
        return $this;
    }
    public function getTitle()
    {
        return $this->_title;
    }
    public function getBody()
    {
        return $this->_body;
    }
    public function getSidebar()
    {
        return $this->_sidebar;
    }
    public function build()
    {
        return new Page_Director($this);
    }
}

class Page_MobileBuilder implements PageBuilder_Interface
{
    private $_title = 'default value for mobile version';
    private $_body = 'default value for mobile version';

    public function setTitle($title)
    {
        $this->_title = $title;
        return $this;
    }
    public function setBody($body)
    {
        $this->_body = $body;
        return $this;
    }
    public function getTitle()
    {
        return $this->_title;
    }
    public function getBody()
    {
        return $this->_body;
    }
    public function getSidebar()
    {
        return null;
    }
    public function build()
    {
        return new Page_Director($this);
    }
}



/**
 * Usage
 */

$pageBuilder = new Page_DesktopBuilder();
$pageBuilder->setTitle('The North')
    ->setBody('The North consists of the northern half of Westeros')
    ->setSidebar('Creator: George R. R. Martin');
$page = $pageBuilder->build();
echo "JSON for desktop:\n", $page, "\n";

$pageBuilder = new Page_MobileBuilder();
$pageBuilder->setTitle('The North')
    ->setBody('The North consists of the northern half of Westeros');
$page = $pageBuilder->build();
echo "JSON for mobile:\n", $page, "\n";


// Output
//  JSON for desktop:
//  {"title":"The North","body":"The North consists of the northern half of Westeros"
//  ,"sidebar":"Creator: George R. R. Martin"}
//
// JSON for mobile:
// {"title":"The North","body":"The North consists of the northern half of Westeros"
// ,"sidebar":null}

As you see here, both builders work with the same context, but one can expect different results depend on chosen builder.


JS/ES5 Example

This time we have a deferred object, which is being populated asynchronously. Let’s say we work on a brand page like on Facebook. We don’t want delaying the page generation by fetching followers and bookmarking rate. Instead we are going to display the page first and then retrieve and add that information on the fly.

/*
 * @category Design Pattern Tutorial
 * @package Builder Sample
 * @author Dmitry Sheiko <me@dsheiko.com>
 * @link http://dsheiko.com
 */
(function() {
// Dummy DataAccess
var DataAccess = (function() {
    return {
        // Here can be XMLHttpRequest
        get : function(controller, params, callback) {
            if (controller == 'getBookmarksNumByPageId')
                callback(42);
            else
                callback([
                    { name: 'Jon Snow', url: 'url1'},
                    { name: 'Barristan Selmy', url: 'url2'},
                    { name: 'Samwell Tarly', url: 'url3'}
                ]);
        }
    };
}());

var PageSocialInfo = function() {
};
var PageSocialInfoBuilder = function(pageId) {
    var info = new PageSocialInfo();
    return {
        build : function() {
            var context = this;
            DataAccess.get('getFollowersByPageId', {'pageId' : pageId}, function(data) {
                info.followers = data;
                if (typeof info.bookmarked !== 'undefined') { // are bookmarks retrieved already
                    context.onMessage(info);
                }
            });
            DataAccess.get('getBookmarksNumByPageId', {'pageId' : pageId}, function(data) {
                info.bookmarked = data;
                if (info.followers !== 'undefined') { // are followers retieved already
                    context.onMessage(info);
                }
            });
        }
    }
}
/**
 * Usage
 */
var PAGE_ID = 1;
var infoBuilder = new PageSocialInfoBuilder(PAGE_ID);
// Data can be retrieved asynchronously, so onMessage handler will serve
infoBuilder.onMessage = function(info) { console.log(info); };
infoBuilder.build();

// Output
// -> Object { followers=[3], bookmarked=42}

}());