Design Patterns by PHP and JavaScript examples
After having your project fully tested, deployed and running, it seems the application architecture is pretty good enough. All the requirements met and everybody is happy. But then as it happens, the requirements change and you, all of sudden, find yourself in the time of troubles. It comes out that some modules easier to hack than to modify. Change of other ones brings endless changes in a cascade of dependent modules. Or you change one module and whole the application starts to collapse like a house of cards. And, of course, you find out that you can’t reuse already written modules for the new tasks, because the encapsulation of the desired parts would take too much risk and work. Robert C. Martin was very accurate naming those symptoms of rotting design as Viscosity, Rigidity, Fragility and Immobility
Well, the architecture tends to be refactored. You equip yourself with the list of heuristics and sniffer over the code for bad smells. Give enough efforts and time and you will find a lot of them. So what are you going to do next? Will you try to find your own solution for every rotting spot just following the “solid” principles ? Surely it comes on your mind that some thousands of programmers been solving the exact issue already for hundred times with different programming languages. It must be a pattern to follow. Here we come, that is exactly what Design Patterns are about.
If you think that when you have to design a module you can just pick a suitable snippet from your archive, give up. It doesn’t work with Design Patterns. They are abstract concepts, which you should feel. When designing a module, you take on account one or more due design patterns to build your own solution based on them. So, how to learn them, how to feel them?
On the latest W3C conference Doug Schepers was asking the audience “Who of you is learning from the code?” and everybody just laughed back. It was a rhetorical question. Everybody learns mostly from the code, but guided by specifications and manuals. For a software engineer an example of good designed code gives much more vision than pages of text explanations. I think the best way to go with Design Patterns, that running each one through the playground. Once implemented an approach, it makes easier to do it again when you need it. And here below I did it for myself.
Design Patterns
Abstract Factory
The pattern allows:
- the system to be independent of how its products are created, composed, and represented;
- to use families of objects together;
- to decouple concrete class from the clients;
- run-time control over object creation process.
PHP Example
When you need your system independent of how products are created, you can use abstract factory. The classical example would be a polymorphic application which can run in different environment (on desktop and mobile platform in here). The application just refers to an abstract product (widget) and the abstract factory takes care of which concrete product (widget) must be used.
<?php
/*
* @category Design Pattern Tutorial
* @package AbstractFactory Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
// Abstract Factory
// File: ./Widget/Factory/AbstractFactory.php
namespace Widget\Factory;
abstract class AbstractFactory
{
abstract public function makeDialog();
abstract public function makeButton();
}
// Concrete Factory
// File: ./Widget/Factory/Desktop.php
namespace Widget\Factory;
class Desktop extends AbstractFactory
{
public function makeDialog()
{
return new \Widget\Dialog\Desktop();
}
public function makeButton()
{
return new \Widget\Button\Desktop();
}
}
// Concrete Factory
// File: ./Widget/Factory/Mobile.php
namespace Widget\Factory;
class Mobile extends AbstractFactory
{
public function makeDialog()
{
return new \Widget\Dialog\Mobile();
}
public function makeButton()
{
return new \Widget\Button\Mobile();
}
}
// Abstract Product
// File: ./Widget/Dialog/iDialog.php
namespace Widget\Dialog;
interface iDialog
{
public function render();
}
// Concrete Product
// File: ./Widget/Dialog/Desktop.php
namespace Widget\Dialog;
class Desktop implements \Widget\Dialog\iDialog
{
public function render()
{
print "jQueryUI based dialog\n";
}
}
// Concrete Product
// File: ./Widget/Dialog/Mobile.php
namespace Widget\Dialog;
class Mobile implements \Widget\Dialog\iDialog
{
public function render()
{
print "jQueryMobile based dialog\n";
}
}
// Abstract Product
// File: ./Widget/Button/iButton.php
namespace Widget\Button;
interface iButton
{
public function render();
}
// Concrete Product
// File: ./Widget/Button/Desktop.php
namespace Widget\Button;
class Desktop implements \Widget\Button\iButton
{
public function render()
{
print "jQueryUI based button\n";
}
}
// Concrete Product
// File: ./Widget/Button/Mobile.php
namespace Widget\Button;
class Mobile implements \Widget\Button\iButton
{
public function render()
{
print "jQueryMobile based button\n";
}
}
// File: ./Config.php
class Config
{
public $platform;
}
// Client
// File: ./Application.php
class Application
{
private $_config;
public function __construct($config)
{
$this->_config = $config;
}
private function _createPlaformSpecificFactory()
{
$className = "\\Widget\\Factory\\" . ucfirst($this->_config->platform);
return new $className;
}
public function build()
{
$factory = $this->_createPlaformSpecificFactory();
$dialog = $factory->makeDialog();
$dialog->render();
$button = $factory->makeButton();
$button->render();
}
}
/**
* Usage
*/
include "./Widget/Factory/AbstractFactory.php";
include "./Widget/Factory/Desktop.php";
include "./Widget/Factory/Mobile.php";
include "./Widget/Dialog/iDialog.php";
include "./Widget/Dialog/Desktop.php";
include "./Widget/Dialog/Mobile.php";
include "./Widget/Button/iButton.php";
include "./Widget/Button/Desktop.php";
include "./Widget/Button/Mobile.php";
include "./Config.php";
include "./Application.php";
$config = new Config();
$config->platform = "mobile";
$app = new Application($config);
$app->build();
// Output:
// jQueryMobile based dialog
// jQueryMobile based button
JavaScript Example
This example shows the case where we have two concrete products. One is real widget and the second is a mock object. The client knows nothing about concrete products, but requesting abstract product, it gets what we want: the mock object when unit-testing and the real widget otherwise.
/*
* @category Design Pattern Tutorial
* @package AbstractFactory Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
(function() {
"use strict";
// Client
var page = (function() {
return {
render: function( options ) {
var widget,
factory = ( options.testing ? mockWidgetFactory : widgetFactory );
if ( factory instanceof AbstractWidgetFactory === false ) {
throw new TypeError( "Argument must be an instance of AbstractWidgetFactory" );
}
widget = factory.makeCarousel();
if ( widget instanceof AbstractWidget === false ) {
throw new TypeError( "Argument must be an instance of AbstractWidget" );
}
widget.render();
}
}
}()),
// Abstract factory
AbstractWidgetFactory = function() {
},
// Concrete factory
mockWidgetFactory = Object.create( new AbstractWidgetFactory(), {
makeCarousel: {
value: function() {
return mockCarousel;
}
}
}),
// Concrete factory
widgetFactory = Object.create( new AbstractWidgetFactory(), {
makeCarousel: {
value: function() {
return carousel;
}
}
}),
// Abstract product
AbstractWidget = function() {
},
// Concrete product
carousel = Object.create( new AbstractWidget(), {
render: {
value: function() {
console.log('Carousel widget rendered');
}
}
}),
// Concrete product
mockCarousel = Object.create( new AbstractWidget(), {
render: {
value: function() {
console.log('Mock carousel widget rendered');
}
}
});
/**
* Usage
*/
page.render({ "testing": true });
page.render({ "testing": false });
// Output
// Mock carousel widget rendered
// Carousel widget rendered
}());
JSA-based Example
Here the JavaScript example using JSA library. The library provides class-based inheritance and support for interfaces. So, you can compare this code to the previous example.
/*
* @category Design Pattern by JSA Tutorial
* @package AbstractFactory Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
(function() {
"use strict";
// Client
var PageClass = function( options ) {
return {
render: function() {
var widget,
factory = ( options.testing ?
MockWidgetFactory.createInstance() : WidgetFactory.createInstance() );
widget = factory.makeCarousel();
widget.render();
}
}
},
// Abstract factory
AbstractWidgetFactory = function() {
},
// Concrete factory
MockWidgetFactory = function() {
return {
__extends__: AbstractWidgetFactory,
makeCarousel: function() {
return MockCarousel.createInstance();
}
}
},
// Concrete factory
WidgetFactory = function() {
return {
__extends__: AbstractWidgetFactory,
makeCarousel: function() {
return Carousel.createInstance();
}
}
},
WidgetInterface = {
render: []
},
// Abstract product
AbstractWidget = function() {
},
Carousel = function() {
return {
__extends__: AbstractWidget,
__implements__: WidgetInterface,
render: function() {
console.log('Carousel widget rendered');
}
}
},
MockCarousel = function() {
return {
__extends__: AbstractWidget,
__implements__: WidgetInterface,
render: function() {
console.log('Mock carousel widget rendered');
}
}
};
/**
* Usage
*/
PageClass.createInstance({ "testing": true }).render();
PageClass.createInstance({ "testing": false }).render();
// Output
// Mock carousel widget rendered
// Carousel widget rendered
}());
Back to the patterns list
Builder
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.
PHP Example
Let us assume we have a document which can be represented in different formats (e.g. as PDF and ePub). So, we make Reader object (director), which build the document using the given convertor (builder). As we have converters Pdf and Epub, we can pass it the Reader to have the desired representation.
<?php
/*
* @category Design Pattern Tutorial
* @package Builder Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
// File: ./Document/Entity.php
namespace Document;
class Entity
{
public $author = "George R. R. Martin";
public $title = "A Song of Ice and Fire";
public $chapters = array("Chapter 1", "Chapter 2");
}
// Director
// File: ./Document/Reader.php
namespace Document;
class Reader
{
public function getDocument(\Document\Entity $document
, \Document\AbstractConvertor $convertor)
{
$convertor->setAuthor($document->author);
$convertor->setTitle($document->title);
foreach ($document->chapters as $chapter) {
$convertor->setText($chapter);
}
return $convertor->getDocument();
}
}
// Abstract builder
// File: ./Document/AbstractConvertor.php
namespace Document;
abstract class AbstractConvertor
{
protected $_buffer;
abstract public function setAuthor($author);
abstract public function setTitle($title);
abstract public function setText($text);
public function getDocument()
{
return$this->_buffer;
}
}
// Concrete builder
// File: ./Document/Convertor/Pdf.php
namespace Document\Convertor;
class Pdf extends \Document\AbstractConvertor
{
public function setAuthor($author)
{
$this->_buffer .= "PDF: Author info {$author}\n";
}
public function setTitle($title)
{
$this->_buffer .= "PDF: Title info {$title}\n";
}
public function setText($text)
{
$this->_buffer .= "PDF: {$text}\n";
}
}
// Concrete builder
// File: ./Document/Convertor/Epub.php
namespace Document\Convertor;
class Epub extends \Document\AbstractConvertor
{
public function setAuthor($author)
{
$this->_buffer .= "ePub: Author info {$author}\n";
}
public function setTitle($title)
{
$this->_buffer .= "ePub: Title info {$title}\n";
}
public function setText($text)
{
$this->_buffer .= "ePub: {$text}\n";
}
}
/**
* Usage
*/
include './Document/Entity.php';
include './Document/Reader.php';
include './Document/AbstractConvertor.php';
include './Document/Convertor/Pdf.php';
include './Document/Convertor/Epub.php';
$doc = new \Document\Entity();
$convertor = new \Document\Convertor\Pdf();
$reader = new \Document\Reader();
print $reader->getDocument($doc, $convertor);
// Output
// PDF: Author info George R. R. Martin
// PDF: Title info The Song of Ice and Fire
// PDF: Chapter 1
// PDF: Chapter 2
JavaScript Example
Our imaginary application populates page with modules. All the modules have the window-like layout with title, close button, resize control. Though, every module provides a unique widget (e.g. carousel, accordion and so on). We have also some themes. Where every theme provides different representations for the modules. Let’s say, one has close button in the right corner and other in the left, layout components styled differently and so on. In order to solve it, we employ moduleManager object (director) which constructs modules using the given theme (builder).
/*
* @category Design Pattern Tutorial
* @package Builder Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
(function() {
"use strict";
// Widget is used to build a product
var AbstractWidget = function() {
},
mockWidget = Object.create( new AbstractWidget(), {
render: {
value: function() {
return "The module has widget";
}
}
}),
// Concrete product
module = null,
// Director
moduleManager = (function() {
var _options = {
title: "",
widget: null,
isClosable: false,
isResizable: false
};
return {
init: function( options ) {
_options = options;
},
construct: function( builder ) {
if ( builder instanceof AbstractBuilder === false ) {
throw new TypeError( "Argument must be an instance of AbstractBuilder" );
}
builder.buildWindow( _options.title );
_options.widget %26%26 builder.attachWidget( _options.widget );
_options.isClosable %26%26 builder.attachCloseButton();
_options.isResizable %26%26 builder.attachResizeControl();
return builder.getModule();
}
}
}()),
// Abstract builder
AbstractBuilder = function() {
},
// Concrete builder
themeA;
// Members to be inherited by every theme
AbstractBuilder.prototype = {
markup: "",
getModule: function() {
return this.markup;
}
};
themeA = Object.create( new AbstractBuilder(), {
buildWindow: {
value: function( title ) {
this.markup += "Module " + title + " represented with ThemeA theme\n";
}
},
attachCloseButton: {
value: function() {
this.markup += "The module has close button\n";
}
},
attachWidget: {
value: function( widget ) {
if ( widget instanceof AbstractWidget === false ) {
throw new TypeError( "Argument must be an instance of AbstractWidget" );
}
this.markup += widget.render();
}
},
attachResizeControl: {
value: function() {
this.markup += "The module has resize control\n";
}
}
});
/**
* Usage
*/
moduleManager.init({
title: "Example",
widget: mockWidget,
isClosable: true,
isResizable: true
});
module = moduleManager.construct( themeA );
console.log( module );
// Output
// Module Example represented with ThemeA theme
// The module has widgetThe module has close button
// The module has resize control
}());
JSA-based Example
The same example, but using JSA library now.
/*
* @category Design Pattern by JSA Tutorial
* @package Builder Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
(function() {
"use strict";
// Widget is used to build a product
var AbstractWidgetClass = function() {},
MockWidgetClass = function() {
return {
__extends__: AbstractWidgetClass,
render: function() {
return "The module has widget";
}
}
},
// Concrete product
module = null,
// Abstract builder
AbstractBuilderClass = function() {
return {
markup: "",
getModule: function() {
return this.markup;
}
}
},
DirectorInterface = {
construct: [ AbstractBuilderClass ]
},
// Director
moduleManagerClass = function( options ) {
return {
__implements__: DirectorInterface,
construct: function( builder ) {
builder.buildWindow( options.title );
options.widget %26%26 builder.attachWidget( options.widget );
options.isClosable %26%26 builder.attachCloseButton();
options.isResizable %26%26 builder.attachResizeControl();
return builder.getModule();
}
}
},
BuilderInterface = {
buildWindow: [ "string" ],
attachCloseButton: [],
attachWidget: [ AbstractWidgetClass ],
attachResizeControl: []
},
// Concrete builder
ThemeAClass = function() {
return {
__extends__: AbstractBuilderClass,
__implements__: BuilderInterface,
buildWindow: function( title ) {
this.markup += "Module " + title + " represented with ThemeA theme\n";
},
attachCloseButton: function() {
this.markup += "The module has close button\n";
},
attachWidget: function( widget ) {
this.markup += widget.render();
},
attachResizeControl: function() {
this.markup += "The module has resize control\n";
}
}
};
/**
* Usage
*/
module = moduleManagerClass.createInstance({
title: "Example",
widget: MockWidgetClass.createInstance(),
isClosable: true,
isResizable: true
}).construct( ThemeAClass.createInstance() );
console.log( module );
// Output
// Module Example represented with ThemeA theme
// The module has widgetThe module has close button
// The module has resize control
}());
Back to the patterns list
Prototype
The pattern allows:
- creating new objects identical or closely resembling existing ones;
- avoiding expense operation when it is required for the initial creating of an object;
- to decouple composition, creating and representation of objects.
PHP Example
Here in the example we have to make a collection of message objects which differ by only email address property. Thus, every element of the collection is an object derived from the given prototype.
<?php
/*
* @category Design Pattern Tutorial
* @package Prototype Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
/**
* Concrete prototype
*/
class Entity_EmailMessage
{
public $headers = array();
public $subject = null;
public $message = null;
public $to = null;
public function headersAsString()
{
$out = "MIME-Version: 1.0\r\n";
foreach ($this->headers as $key => $val) {
$out .= sprintf("%s: %s\r\n", $key, $val);
}
return $out;
}
}
class Lib_PhpNative_Email
{
public function send(Entity_EmailMessage $message)
{
printf ("Following message sent:\n %s\n To: %s\n Subject: %s\n Message: %s\n\n",
$message->headersAsString(), $message->to, $message->subject
, $message->message);
}
}
class Lib_Mailer
{
private $_messages = array();
public function enqueue($email, $messagePrototype)
{
$message = clone $messagePrototype;
$message->to = $email;
$this->_messages[] = $message;
}
public function sendToAll()
{
$sender = new Lib_PhpNative_Email();
array_walk ($this->_messages, array($sender, "send"));
}
}
/**
* Usage
*/
$prototype = new Entity_EmailMessage();
$prototype->headers = array(
'From' => 'Our project <[email protected]>',
'Content-type' => 'text/html; charset="iso-8859-1"',
'Content-Transfer-Encoding' => '8bit',
);
$prototype->subject = 'Newsletter from our project';
$prototype->message = 'Body text....';
$mailer = new Lib_Mailer();
$mailer->enqueue("[email protected]", $prototype);
$mailer->enqueue("[email protected]", $prototype);
$mailer->sendToAll();
// Output:
// Following message sent:
// MIME-Version: 1.0
// From: Our project <[email protected]>
// Content-type: text/html; charset="iso-8859-1"
// Content-Transfer-Encoding: 8bit
//
// To: [email protected]
// Subject: Newsletter from our project
// Message: Body text....
//
// Following message sent:
// MIME-Version: 1.0
// From: Our project <[email protected]>
// Content-type: text/html; charset="iso-8859-1"
// Content-Transfer-Encoding: 8bit
//
// To: [email protected]
// Subject: Newsletter from our project
// Message: Body text....
JS/ES5 Example
This example is very close to PHP one, except to make JS cloning I had to use a trick (clone function).
/*
* @category Design Pattern Tutorial
* @package Prototype Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
(function(){
function Clone() { }
function clone(obj) {
Clone.prototype = obj;
return new Clone();
}
/**
* Concrete prototype
*/
var Row = function(id, title){
return {
id: id,
title: title
}
};
var TestApp = function() {
var _private = {
table: []
}
return {
requestSampleRow: function() {
return new Row(1, 'Sample');
},
pupulateTable: function(sample) {
_private.table.push(sample);
for (var i = 1; i < 5; i++) {
var sampleClone = clone(sample);
sampleClone.id += i;
_private.table.push(sampleClone);
}
},
renderTable: function() {
_private.table.forEach(function(row){
console.log('id: ' + row.id + ', title: ' + row.title);
})
}
}
};
/**
* Usage
*/
var app = new TestApp();
var sample = app.requestSampleRow();
app.pupulateTable(sample);
app.renderTable();
// Output:
// id: 1, title: Sample
// id: 2, title: Sample
// id: 3, title: Sample
// id: 4, title: Sample
// id: 5, title: Sample
}());
Back to the patterns list
Singleton
The pattern allows:
- control over concrete class instantiating so that the only instance could be obtained.
PHP Example
For Registry we need the only instance of the corresponding object, globally accessible. So, Registry is often implemented as a Singleton. By the way, the Registry can be also used to keep instances of objects. This way we may be sure that the same instance consumed every time we request the object from the Registry.
<?php
/*
* @category Design Pattern Tutorial
* @package Singleton Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
class Registry
{
private static $_instance = null;
private $_data = array();
public static function getInstance()
{
if (self::$_instance) {
return self::$_instance;
}
return (self::$_instance = new self());
}
public function __set($name, $value)
{
$this->_data[$name] = $value;
}
public function __get($name)
{
return (isset ($this->_data[$name]) ? $this->_data[$name] : null);
}
}
/**
* Usage
*/
Registry::getInstance()->aVar = 'aValue';
var_dump(Registry::getInstance()->aVar);
JS/ES5 Example
/*
* @category Design Pattern Tutorial
* @package Singleton Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
(function() {
var Registry = (function(){
var data = {};
return {
set : function(name, value) {
data[name] = value;
},
get : function(name) {
return data[name];
}
}
})();
/*
* Usage
*/
Registry.set('aVar', 'aValue');
alert(Registry.get('aVar'));
}());
Back to the patterns list
Delegation
The pattern allows:
- an application design where one object (Delegator) instead of performing a task, delegate it to an associated helper object (Delegates). Delegation can be considered as an extreme form of object composition that can always be used to replace inheritance.
PHP Example
In this example we have Mailer class comprising send method. For the class consumer it seems this Mailer class is performing the send, but in fact, it just delegates the task to another class (MockMailer)
<?php
/*
* @category Design Pattern Tutorial
* @package Delegate Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
/**
* Delegate
*/
class MockMailer
{
public function send($to, $subject, $body)
{
printf ("Message to: %s, subject: %s, body: %s\n\n", $to, $subject, $body);
}
}
/**
* Delegator
*/
class Mailer
{
public function send($to, $subject, $body)
{
$mailer = new MockMailer();
$mailer->send($to, $subject, $body);
}
}
/**
* Usage
*/
$mailer = new Mailer();
$mailer->send("[email protected]", "a subject", "a body");
// Output:
// -> Message to: [email protected], subject: a subject, body: a body
JS/ES5 Example
Here delegation pattern is used to solve classical task of video-player. When client browser doesn’t support HTML5 video tag, the player delegates the task to one of fallback implementations.
/*
* @category Design Pattern Tutorial
* @package Delegate Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
(function() {
/**
* Delegator
*/
var VideoPlayer = function() {
if (window.navigator.userAgent.toLowerCase().match(/msie [0-8]\./i) !== null) {
var delegate = new FlashDelegate();
} else {
var delegate = new Html5Delegate();
}
return {
render : function(data) {
return delegate.render(data);
}
}
}
/**
* Concree Delegate 1
*/
var Html5Delegate = function() {
var _private = {
getVideoEl : function(width, height, poster) {
var videoEl = document.createElement('video');
videoEl.setAttribute('controls', 'controls');
videoEl.setAttribute('preload', 'none');
videoEl.setAttribute('poster', poster);
videoEl.setAttribute('width', width);
videoEl.setAttribute('height', height);
return videoEl;
},
getSourceEl : function(type, src) {
var srcEl = document.createElement('source');
srcEl.setAttribute('type', type);
srcEl.setAttribute('src', src);
return srcEl;
}
}
return {
render : function(data) {
var videoEl = _private.getVideoEl(
data.width, data.height, data.poster);
videoEl.appendChild(_private.getSourceEl('video/mp4', data.mp4Src));
videoEl.appendChild(_private.getSourceEl('video/webm', data.webmSrc));
videoEl.appendChild(_private.getSourceEl('video/ogg', data.oggSrc));
return videoEl;
}
}
}
/**
* Concree Delegate 2
*/
var FlashDelegate = function() {
var FALLBACK_SWF = 'flashmediaelement.swf';
var _private = {
getObjectEl : function(width, height) {
var objectEl = document.createElement('object');
objectEl.setAttribute('type', 'application/x-shockwave-flash');
objectEl.setAttribute('data', FALLBACK_SWF);
objectEl.setAttribute('width', width);
objectEl.setAttribute('height', height);
return objectEl;
},
getParamEl : function(name, value) {
var paramEl = document.createElement('param');
paramEl.setAttribute('name', name);
paramEl.setAttribute('value', value);
return paramEl;
}
}
return {
render : function(data) {
var objectEl = _private.getObjectEl(
data.width, data.height);
objectEl.appendChild(_private.getParamEl('movie', FALLBACK_SWF));
objectEl.appendChild(_private.getParamEl('flashvars', 'controls=true%26poster='
+ data.poster + '%26file=' + data.mp4Src));
return objectEl;
}
}
}
/**
* Usage
*/
var player = new VideoPlayer();
console.log(player.render({
width : 320,
height: 240,
poster: 'poster.jpg',
mp4Src: 'myvideo.mp4',
webmSrc: 'myvideo.webm',
oggSrc: 'myvideo.ogg'
}));
}());
Back to the patterns list
Bridge
The pattern allows:
- to decouple an abstraction from its implementation so, that the two can vary independently;
- deferring the presence of the implementation to the point where the abstraction is utilized.
Comparing the Bridge pattern to the Strategy you can be misled by the apparent similarity. But they are meant for the different purposes. Bridge is a structural pattern and Strategy is a behavioral one. So, with the Strategy you are choosing the algorithm for the concrete operation at runtime. The Bridge pattern provides an alternative to inheritance when there is more than one version of an abstraction.
The Bridge pattern can be confused with the Adapter pattern. Though in the fact, the first pattern is just often implemented using the second one.
PHP Example
Thinking of the Bridge, the first thing which comes on mind is access to different APIs. Here is an example:
<?php
/*
* @category Design Pattern Tutorial
* @package Bridge Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
/**
* Implementor
*/
interface Dao_API_Interface
{
public function fetchProfile($guid);
}
/**
* Concrete implementor 1
*/
class Dao_RemoteServerApi implements Dao_API_Interface
{
public function fetchProfile($guid)
{
return sprintf ("Profile #%d via remote server API\n", $guid);
}
}
/**
* Concrete implementor 2
*/
class Dao_LocalApi implements Dao_API_Interface
{
public function fetchProfile($guid)
{
return sprintf ("Profile #%d via local API\n", $guid);
}
}
/**
* Abstraction
*/
class Dao_User
{
private $_api;
public function __construct(Dao_API_Interface $api)
{
$this->_api = $api;
}
public function fetchProfile($guid)
{
return $this->_api->fetchProfile($guid);
}
}
/**
* Usage
*/
$dao = new Dao_User(new Dao_RemoteServerApi());
print $dao->fetchProfile(1);
// Output:
// Profile #1 via remote server API
JS/ES5 Example
Here, like in the previous example, we provide the client object a bridge to the concrete API.
/*
* @category Design Pattern Tutorial
* @package Bridge Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
(function () {
var SessionStorageApi = (function() {
return {
save: function (name, value) {
console.log('Saved in SessionStorage');
window.sessionStorage[name] = value;
},
get: function (name) {
return window.sessionStorage[name];
}
}
}());
var CookieApi = (function() {
return {
save: function (name, value) {
console.log('Saved in Cookies');
document.cookie = name + "=" + escape(value);
},
get: function (name) {
var key, val, cookieArr = document.cookie.split(";");
for (var i = 0; i < cookieArr.length; i++) {
key = cookieArr[i].substr(0, cookieArr[i].indexOf("="));
val = cookieArr[i].substr(cookieArr[i].indexOf("=") + 1);
key = key.replace(/^\s+|\s+$/g,"");
if (key == name) {
return unescape(val);
}
}
}
}
}());
var NotepadWidget = function(api) {
var DATA_ID = 'noteWidgetText';
return {
get: function() {
return api.get(DATA_ID);
},
save: function(text) {
api.save(DATA_ID, text);
}
}
}
/**
* Usage
*/
var notepad = new NotepadWidget(SessionStorageApi);
notepad.save('Lorem ipsum');
console.log(notepad.get());
// Output:
// Saved in SessionStorage
// Lorem ipsum
}());
Back to the patterns list
Adapter
The pattern allows:
- classes with disparate interfaces to work together through an intermediate object
See also Bridge pattern.
The Adapter pattern is one of easiest to understand. Just imagine that you are on a trip through Italy and staying for a night in a hotel. You need to charge your notebook, but the Italian sockets are incompatible with your plug. So, you ought to use an intermediate device – an adapter.
PHP Example
Most of PHP frameworks implement database abstract access using the Adapter pattern. Thus, you are likely familiar with the approach and can easily follow the example:
<?php
/*
* @category Design Pattern Tutorial
* @package Adapter Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
/**
* Db Factory
*/
class Lib_Db
{
public function __construct(Config $config)
{
$className = sprintf("Lib_Db_Adapter_%s", $config->driver);
if (class_exists($className)) {
$db = new $className();
$db->connect($config);
return $db;
}
}
}
interface Db_Adapter_Interface
{
public function connect(Config $config);
public function fetch($sql);
}
/**
* MySQLi Adapter
*/
class Lib_Db_Adapter_Mysqli implements Db_Adapter_Interface
{
private $_mysqli;
public function connect(Config $config)
{
$this->_mysqli = new mysqli($config->host, $config->user, $config->password
, $config->dbscheme);
}
public function fetch($sql)
{
return $this->_mysqli->query($sql)->fetch_object();
}
}
/**
* MySQLi Pdo
*/
class Lib_Db_Adapter_Pdo implements Db_Adapter_Interface
{
private $_dbh;
public function connect(Config $config)
{
$dsn = sprintf('msqli::dbname=%s;host=%s', $config->dbscheme, $config->host);
$this->_dbh = new PDO($dsn, $config->user, $config->password);
}
public function fetch($sql)
{
$sth = $this->_dbh->prepare($sql);
$sth->execute();
return $sth->fetch();
}
}
/**
* Usage
*/
class Config
{
public $driver = 'Mysqli';
public $host = 'localhost';
public $user = 'test';
public $password = 'test';
public $dbscheme = 'test';
}
$config = new Config();
$db = new Lib_Db($config);
$db->fetch('SELECT * FROM `test`');
JS/ES5 Example
If you write an open-source JS library and want it to get popular, what JS framework would you use? One of the most popular? But why not many of them? You make the main logic on JS, but using helpers of framework (DOM navigation, ES5 shims). It may be not a big deal to make your own interface for those helpers and Adapters to different frameworks.
/*
* @category Design Pattern Tutorial
* @package Adapter Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
(function($, YUI) {
var jQuery_Adapter = function() {
var _node;
return {
find : function(selector) {
_node = $(selector);
return this;
},
setAttr : function(attr, value) {
_node.attr(attr, value);
},
getAttr : function(attr) {
return _node.attr(attr);
}
}
}
var YUI_Adapter = function() {
var _node;
return {
find : function(selector) {
_node = Y.one(selector);
return this;
},
setAttr : function(attr, value) {
_node.set(attr, value);
},
getAttr : function(attr) {
return _node.get(attr);
}
}
}
var Framework = (function() {
var ADAPTER = jQuery_Adapter;
return new ADAPTER();
}());
/**
* Usage
*/
Framework.find('div').set('id', 'something');
}(jQuery, YUI));
Back to the patterns list
Composite
The pattern allows:
- to make an architecture where single object instances and collections of these objects are treated uniformly.
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 <[email protected]>
* @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 <[email protected]>
* @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
}());
Back to the patterns list
Decorator
The pattern allows:
- for extending (decoration) the functionality of a certain object at run-time, independently of other instances of the same class.
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 <[email protected]>
* @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 %26$rowArray)
{
$this->_rowArray = %26$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 <[email protected]>
* @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
}());
Back to the patterns list
Flyweight
The pattern allows:
- to reuse of many objects so, to make the utilization of large numbers of objects more efficient.
The classical example describing the pattern, which you can find everywhere, is objects graphically representing characters. They don’t comprise font-related information, but contains instead a reference to a flyweight glyph object shared by every instance of the same character in the document.
PHP Example
Stack of log messages can be pretty long. Here in the example identical messages are not duplicated. Instead by request the flyweight shared message object returns.
<?php
/*
* @category Design Pattern Tutorial
* @package Flyweight Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
/**
* Flyweight object
*/
class LogEntry
{
private $_message = null;
public function __construct($message)
{
$this->_message = $message;
}
public function getMessage(LogEntryContext $context)
{
return $this->_message . " " . strftime("%b %d, %Y / %H:%M", $context->getTimestamp());
}
}
class LogEntryContext
{
private $_timestamp = null;
public function __construct($timestamp)
{
$this->_timestamp = $timestamp;
}
public function getTimestamp()
{
return $this->_timestamp;
}
}
/**
* FlyweightFactory object
*/
class LogEntryFactory
{
private static $_messages = array();
private static $_callCount = 0;
public static function make($message)
{
$hash = md5($message);
if (!isset (self::$_messages[$hash])) {
self::$_messages[$hash] = new LogEntry($message);
}
self::$_callCount ++;
return self::$_messages[$hash];
}
public static function getInstanceCount()
{
return count(self::$_messages);
}
public static function getRequestCount()
{
return self::$_callCount;
}
}
class Logger
{
private $_logEntries;
private $_timeStamps;
public function __construct()
{
$this->_logEntries = new SplDoublyLinkedList();
$this->_timeStamps = new SplDoublyLinkedList();
}
public function log($message)
{
$this->_logEntries->push(LogEntryFactory::make($message));
$this->_timeStamps->push(new LogEntryContext(time()));
}
public function printMessages()
{
$len = $this->_logEntries->count();
for ($i = 0; $i < $len; $i ++) {
echo $this->_logEntries[$i]->getMessage($this->_timeStamps[$i]), "\n";
}
}
}
/**
* Usage
*/
$logger = new Logger();
$logger->log('Page #1 viewed');
$logger->log('Page #1 updated');
$logger->log('Page #1 viewed');
$logger->log('Page #1 viewed');
$logger->log('Page #1 deleted');
$logger->printMessages();
echo LogEntryFactory::getRequestCount(), " LogEntry instances were requested\n";
echo LogEntryFactory::getInstanceCount(), " LogEntry instances were really created\n";
// Output:
// Page #1 viewed Dec 04, 2011 / 19:04
// Page #1 updated Dec 04, 2011 / 19:04
// Page #1 viewed Dec 04, 2011 / 19:04
// Page #1 viewed Dec 04, 2011 / 19:04
// Page #1 deleted Dec 04, 2011 / 19:04
// 3 LogEntry instances were really created
// 5 LogEntry instances were requested
JS/ES5 Example
/*
* @category Design Pattern Tutorial
* @package Flyweight Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
(function() {
var ERROR_INUSUFFICIENT_ARG_TYPE = 1,
ERROR_INUSUFFICIENT_ARG_VALUE = 2,
ERROR_NODE_IS_UNDEFINED = 3;
var ErrorMap = [];
ErrorMap[ERROR_INUSUFFICIENT_ARG_TYPE] = 'Insufficient argument type';
ErrorMap[ERROR_INUSUFFICIENT_ARG_VALUE] = 'Insufficient argument value';
ErrorMap[ERROR_NODE_IS_UNDEFINED] = 'Node is undefined';
/**
* Flyweight object
*/
var ErrorLogEntry = function(errCode){
var _errCode = errCode;
return {
getMessage: function(context) {
return ErrorMap[_errCode] + " " + context.getDate();
}
}
};
var ErrorLogEntryContext = function(date){
var _date = date;
return {
getDate: function() {
return _date;
}
}
};
/**
* FlyweightFactory object
*/
var ErrorLogEntryFactory = (function(){
var _messages = [], _callCount = 0, _creationCount = 0;
return {
make : function(errCode) {
if (typeof _messages[errCode] === 'undefined') {
_messages[errCode] = new ErrorLogEntry(errCode);
_creationCount += 1;
}
_callCount += 1;
return _messages[errCode];
},
getInstanceCount: function() {
return _creationCount;
},
getRequestCount: function() {
return _callCount;
}
}
}());
var ErrorLogger = function(){
var _errCodes = [], _dates = [];
return {
log: function(errCode) {
_errCodes.push(ErrorLogEntryFactory.make(errCode));
_dates.push(new ErrorLogEntryContext(new Date()));
},
printMessages: function() {
_errCodes.forEach(function(logEntry, inx){
console.log(logEntry.getMessage(_dates[inx]));
});
}
}
};
/**
* Usage
*/
var logger = new ErrorLogger();
logger.log(ERROR_INUSUFFICIENT_ARG_TYPE);
logger.log(ERROR_INUSUFFICIENT_ARG_TYPE);
logger.log(ERROR_INUSUFFICIENT_ARG_VALUE);
logger.log(ERROR_INUSUFFICIENT_ARG_TYPE);
logger.log(ERROR_NODE_IS_UNDEFINED);
logger.printMessages();
console.log(ErrorLogEntryFactory.getRequestCount() + " ErrorLogEntry instances were requested");
console.log(ErrorLogEntryFactory.getInstanceCount() + " LogEntry instances were really created");
// Output:
// Insufficient argument type Mon Dec 05 2011 13:02:30 GMT+0100
// Insufficient argument type Mon Dec 05 2011 13:02:30 GMT+0100
// Insufficient argument value Mon Dec 05 2011 13:02:30 GMT+0100
// Insufficient argument type Mon Dec 05 2011 13:02:30 GMT+0100
// Node is undefined Mon Dec 05 2011 13:02:30 GMT+0100
// 5 ErrorLogEntry instances were requested
// 3 LogEntry instances were really created
}());
Back to the patterns list
Facade
The pattern allows:
- a single interface to a set of interfaces within a system;
- a software library easier to use, understand and test, since the facade has convenient methods for common tasks;
- to make code that uses the library more readable, for the same reason;
- to wrap a poorly-designed collection of APIs with a single well-designed API.
As I have told already the Adapter pattern is used when different interfaces must be accessible as a declared particular one. It is assumed to support a polymorphic behavior. The Facade pattern is used when an easier and simpler interface to work with is required.
PHP Example
<?php
/*
* @category Design Pattern Tutorial
* @package Facade Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
class Lib_String
{
public static function parseHomographs($text)
{
static $homographs = null;
if ($homographs === null) {
// include "../homographs.php"
$homographs = array(); // TILT
}
$text = strtr($text, $homographs);
}
public static function removeControlChars($text)
{
$text = preg_replace('#(?:[\x00-\x1F\x7F]+|(?:\xC2[\x80-\x9F])+)#', '', $text);
}
public static function removeMultipleSpaces($text)
{
$text = preg_replace('# {2,}#', ' ', $text);
}
}
class Model_UserFacade
{
public function getCleanUsername($username)
{
$username = Lib_String::parseHomographs($username);
$username = Lib_String::removeControlChars($username);
$username = Lib_String::removeMultipleSpaces($username);
return trim($username);
}
}
JS/ES5 Example
Addy Osmani used the Facade pattern in his remarkable article Patterns For Large-Scale JavaScript Application Architecture. Here is a simplified extract, showing the usage of Facade.
/*
* @category Design Pattern Tutorial
* @package Facade Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
(function() {
var NotificationWidget = (function() {
var _private = {
data: [],
populate : function( data ) {
this.data = data;
},
show : function() {
console.log('Widget displayed with the data:');
this.data.forEach(function(val){
console.log(val);
});
}
};
return {
facade : function( args ) {
_private.populate(args.data);
if ( args.show ) {
_private.show();
}
}
}
}());
/**
* Usage
*/
NotificationWidget.facade({show: true, data: ['One gets message', 'Another gets like']});
// Output:
// Widget displayed with the data:
// One gets message
// Another gets like
}());
Back to the patterns list
Proxy
The pattern allows:
- access control for the subject object;
- added functionality when an object is accessed.
You may know the pattern from its implementations on network-connection. In programming that is also an intervening layer. Proxy is an object of the same interface with the subject and is meant to control access to the subject object.
PHP Example
The proxy pattern is widely used, though, apparently, the simplest example would be cache proxy. The example actually uses Proxy to implement the Flyweight pattern.
<?php
/*
* @category Design Pattern Tutorial
* @package Proxy Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
/**
* Subject interface
*/
interface Entry_Interface
{
public function get();
}
/**
* Subject
*/
class Entry implements Entry_Interface
{
private $_id;
public function __construct($id)
{
$this->_id;
}
public function get()
{
return "Entry #{$this->_id} retrieved";
}
}
/**
* Proxy
*/
class Entry_ChacheProxy implements Entry_Interface
{
private $_id;
public function __construct($id)
{
$this->_id;
}
public function get()
{
static $entry = null;
if ($entry === null) {
$entry = new Entry($this->_id);
}
return $entry->get();
}
}
/**
* Usage
*/
$entryId = 1;
$entry = new Entry_ChacheProxy($entryId);
echo $entry->get(), "\n"; // loading necessary
echo $entry->get(), "\n"; // loading unnecessary
JS/ES5 Example
Another cache proxy, but this time dealing with asynchronous requests.
/*
* @category Design Pattern Tutorial
* @package Proxy Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
(function() {
/**
* Abstract object for data source
*/
var DataSource = function(url) {
this.url = url;
return {
makeRequest: function(method, paramString) {
var _xhr = new XMLHttpRequest(),
_scope = this;
_xhr.open(method, this.url, true);
_xhr.onreadystatechange = function() {
if (_xhr.readyState === 4 %26%26 _xhr.status == 200 %26%26_xhr.responseText.length) {
_scope.onMessageProxy(JSON.parse(_xhr.responseText));
}
};
_xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
_xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");
_xhr.send(paramString);
},
get : function(params) {
this.makeRequest("POST", params);
},
onMessageProxy: function(data) {
this.onMessage(data);
}
}
};
/**
* Concrete data source
*/
// Let's extends PageDataSource with DataSource
var PageDataSource = function(url) {
this.url = url;
}
PageDataSource.prototype = new DataSource();
PageDataSource.prototype.get = function(pageId) {
this.makeRequest("POST", 'pageId=' + pageId);
};
/**
* Proxy object for the data source
*/
// Let's extends PageDataSourceCacheProxy with PageDataSource
var PageDataSourceCacheProxy = function(url)
{
this.url = url;
this._storage = [];
}
PageDataSourceCacheProxy.prototype = new PageDataSource();
PageDataSourceCacheProxy.prototype.get = function(pageId) {
return (this._storage[pageId]
? this.onMessageProxy(this._storage[pageId])
: this.makeRequest("POST", 'pageId=' + pageId));
};
PageDataSourceCacheProxy.prototype.onMessageProxy = function(data) {
this._storage[data.pageId] = data;
this.onMessage(data);
};
/**
* Usage
*/
var pageId = 2,
src = new PageDataSourceCacheProxy('testController.php');
src.onMessage = function(data) {
// When first time requested, it is delivered by XMLHttpRequest
// when the secind time, just taken from cache
console.log(data);
}
src.get(pageId);
// Let's wait before first request cached and then request
setTimeout(function() {
src.get(pageId)
}, 1000);
})();
Back to the patterns list
Data Access Object
The pattern allows:
- an abstract interface to some type of database or persistence mechanism, providing some specific operations without exposing details of the database.
It is a structural core J2EE pattern alongside with DTO/Entity (Data transfer object) and DBO (Data Business Object). Generally by means of the pattern you can have the application architecture where you business model doesn’t deal with data storage (e.g. DB) directly, but through an abstract layer with a simple interface (CRUD – create, read, update, delete).
The DAO pattern is used by most of mainstream frameworks, though it can be other patterns, but based on DAO. Thereby, Zend Framework has for the data access abstraction layer the Table Data Gateway pattern from Martin Fowler’s POEAA. Symfony uses Doctrine library. Ruby On Rails uses an ActiveRecord implementation bound to the database layer.
PHP Example
The example shows very simple DAO implementation, based on Hypernate interface.
<?php
/*
* @category Design Pattern Tutorial
* @package DAO Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
abstract class Dao_Abstract
{
protected $_connection;
protected $_primaryKey;
protected $_tableName;
public function __construct($connection)
{
$this->_connection = $connection;
}
public function find($value, $key = NULL)
{
if (is_null($key)) {
$key = $this->_primaryKey;
}
return $this->_connection->fetch(sprintf(
"SELECT * FROM %s WHERE %s = '%s'", $this->_tableName, $this->_primaryKey, $value));
}
public function findAll($value, $key = NULL)
{
//..
}
public function insert($assocArray)
{
//..
}
public function update($assocArray)
{
//..
}
public function delete($key = NULL)
{
//..
}
}
class Dao_User extends Dao_Abstract
{
protected $_primaryKey = "userId";
protected $_tableName = "User";
public function findByEmail($email)
{
return $this->find($email, 'email');
}
}
/**
* Usage
*/
$user = Dao_User(Lib_Db::getInstance('masterDb'));
$user->findByEmail('[email protected]');
JS/ES5 Example
/*
* @category Design Pattern Tutorial
* @package DAO Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
(function() {
var DataAccess = function() {
return {
get: function(controller, params, callback) {
// Here can be XMLHttpRequest
callback({
'userId' : 1,
'name' : 'Jon Snow',
'email' : '[email protected]'
});
}
}
}
var Dao_Abstract = function() {
return {
fetch : function(value, key) {
if (typeof key === "undefined") {
key = this._primaryKey;
}
this._dataAccess.get(this._controllerName, {'value' : value, 'key' : key}, this.onMessage);
}
}
}
var Dao_User = function(dataAccess, entity) {
this._dataAccess = new DataAccess();
this._controllerName = 'UserController';
this._primaryKey = 'userId';
}
Dao_User.prototype = new Dao_Abstract();
Dao_User.prototype.fetchByEmail = function(email) {
this.fetch(email, 'email');
}
/**
* Usage
*/
var user = new Dao_User(new DataAccess());
// User can be retrieved asynchronously, so onMessage handler will server to catch output
user.onMessage = function(user) { console.log(user); };
user.fetchByEmail('[email protected]');
// Output
// -> Object { userId=1, name="Jon Snow", email="[email protected]"}
}());
Back to the patterns list
Command
The pattern allows:
- to decouple the invoker object from the handler object;
- multi-level undo;
- callback functionality;
- to keep history of requests;
- to handle requests in any time in any order.
Three terms always associated with the command pattern are client, invoker and receiver. The client instantiates the command object and provides the information required to call the method at a later time. The invoker decides when the method should be called. The receiver is an instance of the class that contains the method’s code.
PHP Example
The pattern is often used for job queues. The functionality, we need, can be added to the queue as jobs. Whilst the queue doesn’t need to know anything of the concrete implementation it is invoking.
<?php
/*
* @category Design Pattern Tutorial
* @package Command Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
/**
* The Receiver classes
*/
abstract class Model_Abstract
{
}
class Model_Search extends Model_Abstract
{
public function index()
{
echo "Site re-indexed\n";
}
}
class Model_Session extends Model_Abstract
{
public function cleanup()
{
echo "Session cleaned up\n";
}
}
class Lib_JobQueue extends SplQueue
{
}
/**
* The Command classes
*/
abstract class Job_Abstract
{
protected $_receiver;
public function __construct(Model_Abstract $receiver) {
$this->_receiver = $receiver;
}
abstract public function execute();
}
class Job_IndexSearch extends Job_Abstract
{
public function execute()
{
$this->_receiver->index();
}
}
class Job_CleanupSessions extends Job_Abstract
{
public function execute()
{
$this->_receiver->cleanup();
}
}
/**
* Usage
*/
$queue = new SplQueue(); // Job Queue
$searchReceiver = new Model_Search();
$sessionReceiver = new Model_Session();
$queue->enqueue(new Job_IndexSearch($searchReceiver));
$queue->enqueue(new Job_CleanupSessions($sessionReceiver));
foreach ($queue as $job) {
$job->execute();
}
// Output:
// Site re-indexed
// Session cleaned up
JS/ES5 Example
A benefit of this particular implementation of the command pattern is that the switch can be used with any end-point object: widget, window, element.
/*
* @category Design Pattern Tutorial
* @package Command Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
(function() {
/**
* The invoker
*/
var SwitcherConstructor = function(showCommand, hideCommand){
return {
show: function() {
showCommand.execute();
},
hide: function() {
hideCommand.execute();
}
}
};
/**
* The receiver
*/
var Widget = (function(){
return {
show : function() {
console.log('Widget displayed');
},
hide : function() {
console.log('Widget hidden');
}
}
}());
/**
* The command to show given widget
*/
var showCommandConstructor = function(receiver) {
return {
execute: function() {
receiver.show();
}
}
};
/**
* The command to hide given widget
*/
var hideCommandConstructor = function(receiver) {
return {
execute: function() {
receiver.hide();
}
}
};
/**
* Usage
*/
var showCommand = new showCommandConstructor(Widget),
hideCommand = new hideCommandConstructor(Widget),
switcher = new SwitcherConstructor(showCommand, hideCommand);
switcher.show();
switcher.hide();
// Output:
// Widget displayed
// Widget hidden
}());
Back to the patterns list
Chain of Responsibility
The pattern allows:
- more than one object an opportunity to handle a request by linking receiving objects together.
The Chain of Responsibility pattern consists of command objects and a series of handlers. Each handling object contains a set of logic that describes the types of command objects that it can handle, and how to pass off those that it cannot handle to the next processing object in the chain. A mechanism also exists for adding new processing objects to the end of this chain.
PHP Example
Here the message is the context to be passed through the chain of handlers. All the handlers implement Logger interface, but each one processes it differently.
<?php
/*
* @category Design Pattern Tutorial
* @package Chain of Responsibility Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
abstract class Logger
{
private $_next = null;
public function setNext(Logger $logger)
{
$this->_next = $logger;
return $this->_next;
}
public function log($message)
{
$this->_log($message);
if ($this->_next !== null) {
$this->_next->log($message);
}
}
abstract protected function _log($message);
}
class EmailLogger extends Logger
{
public function _log($message)
{
echo "Sending via email: ", $message, " \n";
}
}
class ErrorLogger extends Logger
{
protected function _log($message)
{
echo "Sending to stderr: ", $message, " \n";
}
}
class StdoutLogger extends Logger
{
protected function _log($message)
{
echo "Writing to stdout: ", $message, " \n";
}
}
/**
* Usage
*/
$logger = new StdoutLogger();
$logger->setNext(new ErrorLogger())->setNext(new EmailLogger());
$logger->log('Something happened');
// Output:
// Writing to stdout: Something happened
// Sending to stderr: Something happened
// Sending via email: Something happened
JS/ES5 Example
In this example as soon as data object retrieved it is passed through the chain of handlers associated with concrete modules (widgets). Each handler invokes some functionality in the context of its module. It must be said, particularly in JS the solution can be more elegant by using the Publish–Subscribe pattern
/*
* @category Design Pattern Tutorial
* @package Chain of Responsibility Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
(function() {
var CommandChain = function() {
var _commands = [];
return {
addCommand: function(command) {
_commands.push(command);
return this;
},
runCommand: function(args) {
for (var i in _commands) {
_commands[i].onCommand(args);
}
}
};
};
var PageMetadata = (function() {
return {
onCommand: function(args) {
console.log('Page title is set to ' + args.title);
}
};
}());
var PageMainNavigation = (function() {
return {
onCommand: function(args) {
console.log('Menu refreshed to hihlight page ' + args.pageId);
}
};
}());
var PageContent = (function() {
return {
onCommand: function(args) {
console.log('Article title changed to ' + args.title);
console.log('Article content changed to ' + args.content);
}
};
}());
var DataAccess = (function() {
return {
get: function(pageId, chain) {
// Here we request data, let's say it's done
var data = {
title: "In the Seven Kingdoms",
content: "The novel begins with Lord Eddard Stark (Ned) in Winterfell, ancestral home of House Stark, a noble house of the Seven Kingdoms of Westeros and rulers of the North.",
pageId: pageId
};
chain.runCommand(data);
}
};
}());
var PageSiblingNavigation = (function() {
var pageId = 1;
return {
getNext : function() {
var chain = new CommandChain();
chain.addCommand(PageMetadata)
.addCommand(PageMainNavigation)
.addCommand(PageContent);
DataAccess.get(pageId + 1, chain);
}
};
}());
/**
* Usage
*/
PageSiblingNavigation.getNext();
// Output:
// Page title is set to In the Seven Kingdoms
// Menu refreshed to hihlight page 2
// Article title changed to In the Seven Kingdoms
// Article content changed to The novel begins ...
}());
Back to the patterns list
Mediator
The pattern allows:
- loose coupling by encapsulating the way disparate sets of objects interact and communicate with each other;
- to maintain an application architecture where too many relationships exist and common point of control communication is required.
With the Mediator pattern, communication between objects is encapsulated with a mediator object. Objects no longer communicate directly with each other, but instead communicate through the mediator. This reduces the dependencies between communicating objects, thereby lowering the coupling.
PHP Example
Let us assume, we have a career site. As candidates apply we store their job applications. HR assistants change application status according to defined workflow. When a job application gets the status ‘Candidate is hired’, the number of open vacancies for the job position must be decremented. We could just call JobPosition model from within update method of JobApplication model. But department managers want to be notified when candidates are hired. Later can come new requirements. So, we will have mediator object notifying any number of the objects interested in the event.
<?php
/*
* @category Design Pattern Tutorial
* @package Mediator Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
class Model_JobApplication
{
const STATUS_HIRED = 7;
private $_mediator;
public function __construct(Model_Mediator_Interface $mediator = null)
{
$this->_mediator = $mediator;
}
public function updateStatus($id, $newStatusId)
{
if (!is_null($this->_mediator)) {
$this->_mediator->update($this, array(
"statusId" => $newStatusId,
"appId" => $id,
"posId" => 1 // TILT
));
}
}
}
class Model_JobPosition
{
public function updateAsStatusChanges($data)
{
if ($data['statusId'] === Model_JobApplication::STATUS_HIRED) {
echo 'Number of open vacancies decremented', "\n";
}
}
}
class Model_JobPositionObserver
{
// Observers and job positions on which they are subscibed for status updates
private $_subscription = array(
'[email protected]' => array(1 => array(Model_JobApplication::STATUS_HIRED)),
'[email protected]' => array(1 => array(Model_JobApplication::STATUS_HIRED))
);
public function updateAsStatusChanges($data)
{
foreach ($this->_subscription as $mail => $positions) {
if (isset ($positions[$data['posId']])
%26%26 in_array($data['statusId'], $positions[$data['posId']])) {
echo $mail . ' notified', "\n";
}
}
}
}
interface Model_Mediator_Interface
{
public function update($origObject, $data);
}
class Model_Mediator_JobApplicationStatus implements Model_Mediator_Interface
{
private $_subscribers = array('JobPosition', 'JobPositionObserver');
public function update($origObject, $data)
{
foreach ($this->_subscribers as $subscriber) {
$subscriberClass = 'Model_' . $subscriber;
if (!($origObject instanceof $subscriberClass)) {
$object = new $subscriberClass();
$object->updateAsStatusChanges($data);
}
}
}
}
/**
* Usage
*/
$model = new Model_JobApplication(new Model_Mediator_JobApplicationStatus());
$id = 1;
$newStatusId = Model_JobApplication::STATUS_HIRED;
$model->updateStatus($id, $newStatusId);
// Output:
// Number of open vacancies decremented
// [email protected] notified
// [email protected] notified
JS/ES5 Example
JS is very good for building Event-driven architecture. Here in the example we have widgets communication through the mediator using PubSub communication pattern. The module talking to others just publishes an event attaching the data to it. All the modules subscribed to the event will be notified and received the sent data.
/*
* @category Design Pattern Tutorial
* @package Mediator Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
(function() {
// Mediator by Addy Osmani
var mediator = (function(){
var subscribe = function(channel, fn){
if (!mediator.channels[channel]) mediator.channels[channel] = [];
mediator.channels[channel].push({ context: this, callback: fn });
return this;
},
publish = function(channel){
if (!mediator.channels[channel]) return false;
var args = Array.prototype.slice.call(arguments, 1);
for (var i = 0, l = mediator.channels[channel].length; i < l; i++) {
var subscription = mediator.channels[channel][i];
subscription.callback.apply(subscription.context, args);
}
return this;
};
return {
channels: {},
publish: publish,
subscribe: subscribe,
installTo: function(obj){
obj.subscribe = subscribe;
obj.publish = publish;
}
};
}());
var ForgetPasswordFormWidget = (function() {
var _handler = {
AuthServicesLocked : function(data) {
console.log('Forget password form locked');
},
AuthServicesUnlocked: function(data) {
console.log('Forget password form unlocked');
}
}
subscribe('AuthServicesLocked', _handler.AuthServicesLocked );
subscribe('AuthServicesUnlocked', _handler.AuthServicesUnlocked);
return {
}
}());
mediator.installTo(ForgetPasswordFormWidget);
var LoginFormWidget = (function() {
return {
login : function() {
// Imagine, it sends here request to the server
console.log('Login form locked');
this.publish('AuthServicesLocked', {});
console.log('Login form unlocked');
this.publish('AuthServicesUnlocked', {});
}
}
}());
mediator.installTo(LoginFormWidget);
/**
* Usage
*/
LoginFormWidget.login();
// Output
// Login form locked
// Forget password form locked
// Login form unlocked
// Forget password form unlocked
}());
Back to the patterns list
Iterator
The pattern allows:
- for access to the elements of an aggregate object without allowing access to its underlying representation;
- a uniform interface for traversal.
With the Iterator pattern users can traverse various types of data sets without worrying about the underlying implementation of the collection.
PHP Example
PHP already comprises Iterator interface. Besides, SPL contains a set of extensible Iterator implementers. Here in the example I’m just using one of them.
<?php
/*
* @category Design Pattern Tutorial
* @package Iterator Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
class Dao_News
{
public function fetchAll()
{
$raws = array(
(object)array("id" => 1, "title" => "Title 1"),
(object)array("id" => 2, "title" => "Title 2"),
(object)array("id" => 3, "title" => "Title 3"),
);
return new ArrayIterator($raws);
}
}
/**
* Usage
*/
$dao = new Dao_News();
$newIt = $dao->fetchAll();
for ($newIt->rewind(); $newIt->valid(); $newIt->next()) {
echo $newIt->current()->title, "\n";
}
// Output:
// Title 1
// Title 2
// Title 3
JS/ES5 Example
/*
* @category Design Pattern Tutorial
* @package Iterator Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
(function() {
var ArrayIterator = function(data) {
var _key = 0,
_valid = !!data.length;
return {
next: function() {
var max = data.length - 1;
_valid = _key < max ? !!(++_key) : false;
},
rewind: function() {
_valid = !!data.length;
_key = 0;
},
valid: function() {
return _valid;
},
current: function() {
return data[_key];
},
key: function() {
return _key;
}
};
};
/**
* Usage
*/
var it = new ArrayIterator(["first", "second", "third"]);
for (it.rewind(); it.valid(); it.next()) {
console.log(it.current());
}
// Output:
// first
// second
// third
}());
Back to the patterns list
Memento
The pattern allows:
- for capturing and externalizing an object’s internal state so that it can be restored later, all without violating encapsulation.
The Memento pattern is implemented with two objects: the originator and a caretaker. The originator is an object that has an internal state. The caretaker performs an operation on the originator, but can undo the change when it needed. The caretaker requests memento object from the originator. Then it applies the operation. The caretaker can roll back the originator to the initial state returning the memento object to the originator.
PHP Example
The example describes a restorable iterator. Client module traversing such iterator can mark it state and rollback after many iterations if it necessary. Imagine you analyzing source code iterating from the array given by token_get_all(). You can only know if you are already on the desired position after checking several sibling elements further. Once confirmed the element sequence is true you can easily move to its beginning.
<?php
/*
* @category Design Pattern Tutorial
* @package Memento Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
/**
* Memento
*/
class Memento
{
private $_originator;
public function __construct(%26$originator)
{
$this->_originator = %26$originator;
}
public function save()
{
$this->_state = $this->_originator->getState();
}
public function restore()
{
$this->_originator->setState($this->_state);
}
}
/**
* Originator
*/
interface Restorable
{
public function getState();
public function setState($state);
}
class RestorableIterator implements Iterator, Restorable
{
private $_data;
private $_cursor = 0;
private $_valid = false;
public function __construct(array $data = array())
{
$this->_data = $data;
$this->_valid = (bool)count($this->_data);
}
public function current()
{
return $this->_data[$this->_cursor];
}
public function key()
{
return $this->_cursor;
}
public function getState()
{
return $this->_cursor;
}
public function setState($state)
{
$this->_cursor = $state;
}
public function next()
{
$max = count($this->_data) - 1;
$this->_valid = $this->_cursor < $max ? (bool)(++$this->_cursor) : false;
}
public function rewind()
{
$this->_valid = (bool)count($this->_data);
$this->_cursor = 0;
}
public function valid()
{
return $this->_valid;
}
}
/**
* Caretaker
*/
$it = new RestorableIterator(array('a', 'b', 'c', 'd'));
$itMemento = new Memento($it);
$it->next();
$itMemento->save();
$it->next();
$it->next();
echo "Current value ", $it->current(), "\n";
$itMemento->restore();
echo "Current value ", $it->current(), "\n";
// Output:
// Current value d
// Current value b
JS/ES5 Example
Here is a classical bookmark implementation. OpenDocument object state keeps reader’s position (page). It can be bookmarked and any of bookmarks can be respectively restored.
/*
* @category Design Pattern Tutorial
* @package Memento Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
(function() {
/**
* Memento
*/
var PageBookmarker = function(doc) {
var _private = {
bookmarks: []
};
return {
savePage : function(name) {
if (typeof name !== 'string' || name.length === 0) {
throw new TypeError('Insufficient type');
}
_private.bookmarks[name] = doc.getPage();
},
restorePage : function(name) {
if (typeof _private.bookmarks[name] === 'undefined') {
throw new Error('Bookmark not found');
}
doc.setPage(_private.bookmarks[name]);
}
}
};
/**
* Originator
*/
var OpenDocument = function() {
var _private = {
page: 1
};
return {
setPage: function(page) {
_private.page = page;
},
getPage: function() {
return _private.page;
}
}
};
/**
* Usage
*/
var doc = new OpenDocument(),
bookmarker = new PageBookmarker(doc);
doc.setPage(2);
bookmarker.savePage('test');
doc.setPage(4);
console.log('Current page ' + doc.getPage());
bookmarker.restorePage('test');
console.log('Saved page ' + doc.getPage());
// Output:
// Current page 4
// Saved page 2
}());
Back to the patterns list
Observer
The pattern allows:
- one or more objects be notified of state changes in other objects within the system;
- broadcasting
In the Observer pattern, Subject object has collection of Observer objects. When the method ‘notify’ of Subject object is called, all the attached Observers notified.
PHP Example
I find the pattern very useful for attaching plugins and cross-cutting concerns. In this example I intended to show how you can make your models pluginable.
<?php
/*
* @category Design Pattern Tutorial
* @package Observer Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
class Plugin_Bookmark implements SplObserver
{
public function deleteByUserId($userId)
{
printf ("Bookmarks of user %d deleted\n", $userId);
}
public function update(SplSubject $subject)
{
$userId = $subject->getId();
$this->deleteByUserId($userId);
}
}
class Plugin_Liking implements SplObserver
{
public function deleteByUserId($userId)
{
printf ("Likings of user %d deleted\n", $userId);
}
public function update(SplSubject $subject)
{
$userId = $subject->getId();
$this->deleteByUserId($userId);
}
}
abstract class Pluginable_Abstract implements SplSubject
{
// Array of observers
private $_observers = array();
public function attach(SplObserver $observer)
{
$hash = spl_object_hash($observer);
if (!isset ($this->_observers[$hash])) {
$this->_observers[$hash] = $observer;
}
}
public function detach(SplObserver $observer)
{
unset($this->_observers[spl_object_hash($observer)]);
}
/**
* Implement SplSubject method
*/
public function notify()
{
foreach ($this->_observers as $value) {
$value->update($this);
}
}
}
class Model_Abstract extends Pluginable_Abstract
{
// Common and abstract properies and methods of Models
}
class Model_User extends Model_Abstract
{
private $_userId;
public function setId($userId)
{
$this->_userId = $userId;
}
public function getId()
{
return $this->_userId;
}
public function delete($userId)
{
$this->setId($userId);
printf ("User %d deleted\n", $userId);
$this->notify();
}
}
/**
* Usage
*/
$userId = 1;
$model = new Model_User();
$model->attach(new Plugin_Bookmark());
$model->attach(new Plugin_Liking());
$model->delete($userId);
// Output
// -> User 1 deleted
// -> Bookmarks of user 1 deleted
// -> Likings of user 1 deleted
JS/ES5 Example
/*
* @category Design Pattern Tutorial
* @package Observer Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
(function() {
/**
* Concrete observer
*/
var SilverLightFallbackPlugin = function() {}
SilverLightFallbackPlugin.prototype.update = function(subject) {
// Renders SilverLight embedding into subject.node.viewport element
console.log('SilverLight fallback is shown');
}
/**
* Abstract subject
*/
var Subject = function() {
var _observers = [];
return {
attach : function(observer) {
_observers.push(observer);
},
notify : function() {
for (var i in _observers) {
_observers[i].update(this);
}
}
};
};
/**
* Concrete subject
*/
var VideoPlayer = function(settings) {
// Attaches all given observers
for (var i in settings.plugins) {
this.attach(settings.plugins[i]);
}
//...
if (this.isVideoTagSupportAvailable()) {
this.renderUI();
this.syncUI();
} else {
this.renderFallback();
}
};
VideoPlayer.prototype = new Subject();
VideoPlayer.prototype.node = {
viewport : null
};
VideoPlayer.prototype.renderUI = function() {
//...
}
VideoPlayer.prototype.syncUI = function() {
//...
}
VideoPlayer.prototype.isVideoTagSupportAvailable = function() {
//...
return false;
}
VideoPlayer.prototype.renderFallback = function() {
//...
this.notify();
}
/**
* Usage
*/
new VideoPlayer({ plugins: [ new SilverLightFallbackPlugin() ]});
// Output:
// -> SilverLight fallback is shown
})();
Back to the patterns list
Strategy
The pattern allows:
- to build a neat architecture where many versions of an algorithm are required to perform the task;
- to encapsulate conditional logic of switch statement;
- to define the behavior of a class at run-time.
The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. It lets the algorithm vary independently from clients that use it.
PHP Example
Only 5 years ago semantic web standards seemed as utopia. Now you can find plenty of real appliances. For example, Google parses meta-information from your pages when it is provided as a Rich Snippet. Currently Google supports Microdata, Microformat and Rdf wrappers. What would you do if you are required to wrap user profiles into Rich Snippets? You can write a strategy object, which turns the profile into a Microformat hCard. Now the profile can be obtained as a RichSnippet. Requirement changed? They want another wrapper? Just add a new strategy.
<?php
/*
* @category Design Pattern Tutorial
* @package Strategy Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
class Profile
{
public $data;
protected $_strategy;
public function __construct(array $data)
{
$this->data = $data;
}
public function setStrategyContext(RichSnippetStrategyAbstract $strategy)
{
$this->_strategy = $strategy;
}
public function get() {
return $this->_strategy->get($this);
}
}
abstract class RichSnippetStrategyAbstract
{
protected $_cardTemplate;
protected $_propertyTpls;
protected function _getReplacePairs($properties)
{
$replPairs = array();
foreach ($properties as $property => $val) {
if (is_array ($val)) {
$val = $this->_process($val);
}
$replPairs['{' . $property . '}'] =
strtr($this->_propertyTpls[$property], array('{value}' => $val));
}
return $replPairs;
}
protected function _process(array $data)
{
if (!isset ($data["template"]) || !isset ($data["properties"])) {
throw new Exception('Input data structure is not correct');
}
return strtr($data["template"], $this->_getReplacePairs($data["properties"]));
}
public function get(Profile $context)
{
$card = $this->_process($context->data);
return sprintf($this->_cardTpl, $card);
}
}
class ProfileAsMicrodataStrategy extends RichSnippetStrategyAbstract
{
protected $_cardTpl = '<div itemscope itemtype="http://data-vocabulary.org/Person">%s</div>';
protected $_propertyTpls = array(
'name' => '<span itemprop="name">{value}</span>',
'nickname' => '<span itemprop="nickname">{value}</span>',
'url' => '<a href="{value}" itemprop="url">{value}</a>',
'address' => '<span itemprop="address" itemscope itemtype="http://data-vocabulary.org/Address">{value}</span>',
'locality' => '<span itemprop="locality">{value}</span>',
'region' => '<span itemprop="region">{value}</span>',
'title' => '<span itemprop="title">{value}</span>',
'org' => '<span itemprop="affiliation">{value}</span>');
}
class ProfileAsMicroformatStrategy extends RichSnippetStrategyAbstract
{
protected $_cardTpl = '<div class="vcard">%s</div>';
protected $_propertyTpls = array(
'name' => '<span class="fn">{value}</span>',
'nickname' => '<span class="nickname">{value}</span>',
'url' => '<a href="{value}"class="url">{value}</a>',
'address' => '<span class="adr">{value}</span>',
'locality' => '<span class="locality">{value}</span>',
'region' => '<span class="region">{value}</span>',
'title' => ' <span class="title">{value}</span>',
'org' => '<span class="org">{value}</span>');
}
class ProfileAsRdfStrategy extends RichSnippetStrategyAbstract
{
protected $_cardTpl = '<div xmlns:v="http://rdf.data-vocabulary.org/#" typeof="v:Person">%s</div>';
protected $_propertyTpls = array(
'name' => '<span property="v:name">{value}</span>',
'nickname' => '<span property="v:nickname">{value}</span>',
'url' => '<a href="{value}" rel="v:url">{value}</a>',
'address' => '<span rel="v:address"><span typeof="v:Address">{value}</span></span>',
'locality' => '<span property="v:locality">{value}</span>',
'region' => '<span property="v:region">{value}</span>',
'title' => '<span property="v:title">{value}</span>',
'org' => '<span property="v:affiliation">{value}</span>');
}
/**
* Usage
*/
$profileData = array (
'template' =>
'My name is {name}, but friends call me {nickname}.'
. ' Here is my home page: {url}.'
. ' Now I live in {address} and work as a {title} at {org}.',
'properties' => array (
'name' => 'Dmitry Sheiko',
'nickname' => 'Dima',
'url' => 'http://dsheiko.com',
'address' => array (
'template' => '{locality}, {region}',
'properties' => array (
'locality' => 'Frankfurt am Main',
'region' => 'Germany',
)
),
'title' => 'web-developer',
'org' => 'Crytek',
)
);
$profile = new Profile($profileData);
$profile->setStrategyContext(new ProfileAsMicrodataStrategy());
echo $profile->get(), "\n";
$profile->setStrategyContext(new ProfileAsMicroformatStrategy());
echo $profile->get(), "\n";
// Output
// -> <div itemscope itemtype="http://data-vocabulary.org/Person">My ...
// -> <div class="vcard">My name is <span class="fn">Dmitry Sheiko ...
JS/ES5 Example
We have a set of video player parameters. In order to render the video player based on them we can choose concrete strategy. So it can be rendered as HTML5 player or as Flash player.
/*
* @category Design Pattern Tutorial
* @package Strategy Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
(function() {
var VideoPlayer = function(data) {
var _strategy = null,
_data = data;
return {
data : _data,
setStrategyContext : function(strategy) {
_strategy = strategy;
},
render : function() {
return _strategy.render(this);
}
}
}
var VideoPlayerViaHTML5 = function() {
var _private = {
getVideoEl : function(width, height, poster) {
var videoEl = document.createElement('video');
videoEl.setAttribute('controls', 'controls');
videoEl.setAttribute('preload', 'none');
videoEl.setAttribute('poster', poster);
videoEl.setAttribute('width', width);
videoEl.setAttribute('height', height);
return videoEl;
},
getSourceEl : function(type, src) {
var srcEl = document.createElement('source');
srcEl.setAttribute('type', type);
srcEl.setAttribute('src', src);
return srcEl;
}
}
return {
render : function(context) {
var videoEl = _private.getVideoEl(
context.data.width, context.data.height, context.data.poster);
videoEl.appendChild(_private.getSourceEl('video/mp4', context.data.mp4Src));
videoEl.appendChild(_private.getSourceEl('video/webm', context.data.webmSrc));
videoEl.appendChild(_private.getSourceEl('video/ogg', context.data.oggSrc));
return videoEl;
}
}
}
var VideoPlayerViaFlash = function() {
var FALLBACK_SWF = 'flashmediaelement.swf';
var _private = {
getObjectEl : function(width, height) {
var objectEl = document.createElement('object');
objectEl.setAttribute('type', 'application/x-shockwave-flash');
objectEl.setAttribute('data', FALLBACK_SWF);
objectEl.setAttribute('width', width);
objectEl.setAttribute('height', height);
return objectEl;
},
getParamEl : function(name, value) {
var paramEl = document.createElement('param');
paramEl.setAttribute('name', name);
paramEl.setAttribute('value', value);
return paramEl;
}
}
return {
render : function(context) {
var objectEl = _private.getObjectEl(
context.data.width, context.data.height);
objectEl.appendChild(_private.getParamEl('movie', FALLBACK_SWF));
objectEl.appendChild(_private.getParamEl('flashvars', 'controls=true%26poster='
+ context.data.poster + '%26file=' + context.data.mp4Src));
return objectEl;
}
}
}
var context = new VideoPlayer({
width : 320,
height: 240,
poster: 'poster.jpg',
mp4Src: 'myvideo.mp4',
webmSrc: 'myvideo.webm',
oggSrc: 'myvideo.ogg'
});
if (window.navigator.userAgent.toLowerCase().match(/msie [0-8]\./i) !== null) {
context.setStrategyContext(new VideoPlayerViaFlash());
} else {
context.setStrategyContext(new VideoPlayerViaHTML5());
}
console.log(context.render());
}());
Back to the patterns list
State
The pattern allows:
- to build an architecture where the behavior of an object depends on its state;
- for an object to partially change its type at run-time.
Let’s take a drawing program. The program has a mouse cursor, which can act as one of several tools. The cursor maintains an internal state representing the tool currently in use. When a tool-dependent method is called, the method call is passed on to the cursor’s state. Each tool corresponds to a state.
PHP Example
The logger provided in the example has internal state, which simply saying if the DB connection is still available. The default behavior is to log the message into DB. Though when state is changed (Db unavailable), the behavior changes as well. The logger pushes the message into the file.
<?php
/*
* @category Design Pattern Tutorial
* @package State Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
/**
* The state interface and two implementations
*/
interface State_Logging_Interface
{
public function log(Lib_Logger $context);
}
class State_Logging_File implements State_Logging_Interface
{
public function log(Lib_Logger $context)
{
echo $context->getMessage(), ' logged into file', "\n";
}
}
class State_Logging_Db implements State_Logging_Interface
{
private static function _isDbAvailable()
{
static $counter = false;
$counter = !$counter;
return $counter;
}
public function log(Lib_Logger $context)
{
if ($this->_isDbAvailable()) {
echo $context->getMessage(), ' logged into DB', "\n";
} else {
$context->setState(new State_Logging_File());
$context->log($context->getMessage());
$context->log('DB connection is not available');
}
}
}
/**
* Context class
*/
class Lib_Logger
{
private $_state;
private $_message;
public function __construct()
{
// Default state
$this->_state = new State_Logging_Db();
}
public function setState(State_Logging_Interface $state)
{
$this->_state = $state;
}
public function getMessage()
{
return $this->_message;
}
public function log($message )
{
$this->_message = $message;
$this->_state->log($this, $message);
}
}
/**
* Usage
*/
$logger = new Lib_Logger();
$logger->log('Message 1');
$logger->log('Message 2');
$logger->log('Message 3');
// Output:
// Message 1 logged into DB
// Message 2 logged into file
// DB connection is not available logged into file
// Message 3 logged into file
JS/ES5 Example
Imagine user interface containing list of users. Above the table there is a select combo-box to choose which operation you would like to apply on the users selected in the list. On-change event on this control element causes change of the widget status. Thus, when you submit the form, the widget will call the proper controller according to the actual widget state.
/*
* @category Design Pattern Tutorial
* @package State Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
(function(document) {
/**
* Helps to preserve calling context
*/
var Util = {
proxy : function (method, context) {
if (typeof method == 'string') method = context[method];
var cb = function () {method.apply(context, arguments);}
return cb;
}
};
/**
* State objects
*/
var ManageUserListBlockState = function(widget) {
return {
submit: function() {
widget.node.form.action = 'BLOCK_CONTROLLER_URI';
widget.node.form.submit();
}
}
}
var ManageUserListDeleteState = function(widget) {
return {
submit: function() {
widget.node.form.action = 'DELETE_CONTROLLER_URI';
widget.node.form.submit();
}
}
}
/**
* Base constructor for Widgets
*/
var WidgetAbstract = {
init: function(settings) {
this.node = {};
this.node.boundingBox = document.querySelector(settings.boundingBox);
if (this.hasOwnProperty('PARSE_HTML')) {
for (var i in this.PARSE_HTML) {
this.node[i] = document.querySelector(this.PARSE_HTML[i]);
}
}
if (this.hasOwnProperty('syncUI')) {
this.syncUI();
}
if (this.hasOwnProperty('renderUI')) {
this.renderUI();
}
}
}
/**
* Widget contains submit action depending on states
*/
var ManageUserListWidget = function(settings) {
var _handler = {
onUpdateOptionChange : function(e) {
if (this.node.selectUpdateOptions.value === 'delete') {
this.setState(new ManageUserListDeleteState(this));
} else {
this.setState(new ManageUserListBlockState(this));
}
},
onFormSubmit: function(e) {
e.preventDefault();
this.state.submit();
}
}
return {
state : null,
init: function() {
WidgetAbstract.init.call(this, settings);
this.state = new ManageUserListBlockState(this); // default state
},
PARSE_HTML: {
form : "form",
selectUpdateOptions: "select"
},
syncUI: function() {
this.node.form.addEventListener('submit', Util.proxy(_handler.onFormSubmit, this), false);
this.node.selectUpdateOptions.addEventListener('change'
, Util.proxy(_handler.onUpdateOptionChange, this), false);
},
setState: function(state) {
this.state = state;
}
}
}
/**
* Usage
*/
var widget = new ManageUserListWidget({boundingBox: 'div.userList'});
widget.init();
}(document));
Back to the patterns list
Visitor
The pattern allows:
- for one or more operations to be applied to a set of objects at run-time, decoupling the operations from the object structure.
The Visitor pattern is a way of separating an algorithm from an object structure on which it operates. A practical result of this separation is the ability to add new operations to existing object structures without modifying those structures. It is one way to easily follow the OCP (open/closed principle).
PHP Example
Following example shows how hierarchical structure of objects of the same interface (Composite pattern) can be printed. Instead of creating “print” methods for each class (Book, Chapter, and Article), a single class (Reporter) performs the required printing action.
<?php
/*
* @category Design Pattern Tutorial
* @package Visitor Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
abstract class Content
{
public $title;
abstract public function save();
}
class Book extends Content
{
public $author;
public $isbn;
public $chapters = array();
public function addItem($chapter)
{
$this->chapters[] = $chapter;
}
public function save()
{
//..
}
public function accept(ContentVisitor $visitor)
{
$visitor->visit($this);
array_map(function($element) use ($visitor) {
$element->accept($visitor);
}, $this->chapters);
}
}
class Chapter extends Content
{
public $articles = array();
public function addItem($article)
{
$this->articles[] = $article;
}
public function save()
{
//..
}
public function accept(ContentVisitor $visitor)
{
$visitor->visit($this);
array_map(function($element) use ($visitor) {
$element->accept($visitor);
}, $this->articles);
}
}
class Artile extends Content
{
public $text = "...";
public function save()
{
//..
}
public function accept(ContentVisitor $visitor)
{
$visitor->visit($this);
}
}
interface ContentVisitor
{
public function visit(Content $content);
}
class Reporter implements ContentVisitor
{
public function visit(Content $content)
{
echo "\nObject: ", get_class($content), " \n";
foreach ($content as $property => $value) {
echo $property . ": ", $value, " \n";
}
}
}
/**
* Usage
*/
$book1 = new Book();
$book1->title = "Clean Code A Handbook of Agile Software Craftsmanship";
$book1->author = "Robert C. Martin";
$book1->isbn = "0132350882";
$chapter1 = new Chapter();
$chapter1->title = "Chapter 17: Smells and Heuristics";
$article1 = new Artile();
$article1->title = "C1: Inappropriate Information";
$article2 = new Artile();
$article2->title = "C2: Obsolete Comment";
$article3 = new Artile();
$article3->title = "C3: Redundant Comment";
$chapter1->addItem($article1);
$chapter1->addItem($article2);
$chapter1->addItem($article3);
$book1->addItem($chapter1);
$book1->accept(new Reporter());
// Output:
// Object: Book
// author: Robert C. Martin
// isbn: 0132350882
// chapters: Array
// title: Clean Code A Handbook of Agile Software Craftsmanship
//
// Object: Chapter
// articles: Array
// title: Chapter 17: Smells and Heuristics
//
// Object: Artile
// text: ...
// title: C1: Inappropriate Information
//
// Object: Artile
// text: ...
// title: C2: Obsolete Comment
//
// Object: Artile
// text: ...
// title: C3: Redundant Comment
JS/ES5 Example
Now consider G+ feed. Let’s say we are to develop something alike. When somebody comments on a user post the event is fired. ComentList object is aware, so it updates the list of comments on the user side. But that is not enough. User expects number of unread notifications increases and a new message in the notification list. Both object can be notified as visitors for CommentList object.
/*
* @category Design Pattern Tutorial
* @package Visitor Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
(function() {
/**
* Helper function, which performs synchronous XMLHttpRequest
*/
var Gateway = {};
Gateway.post = function(url, data, callback) {
var _xhr = new XMLHttpRequest();
_xhr.open("POST", url, false);
_xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
_xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");
_xhr.send(data);
if (_xhr.status == 0) {
callback (JSON.parse(_xhr.responseText));
}
};
var Session = {
userId : 1
};
var CommentList = function(article) {
var _article = article;
return {
arrivals : [],
acceptVisitor : function(visitor) {
visitor.visitCommentList(this);
},
update: function() {
Gateway.post("/comment/getAllNew", 'articleId=' + _article.id, function(data){
this.arrivals = data;
});
}
}
}
var NotificationList = function() {
return {
list : [],
visitCommentList : function(commentList) {
for (var i in commentList.arrivals) {
var message = commentList.arrivals[i];
if (message.targetUserId == Session.userId) {
this.list.push('New comment from ' + message.targetUserName);
}
}
}
}
}
var NotificationIndicator = function() {
return {
counter: 0,
visitCommentList : function(commentList) {
for (var i in commentList.arrivals) {
var message = commentList.arrivals[i];
if (message.targetUserId == Session.userId) {
this.counter ++;
}
}
}
}
}
/**
* Usage
*/
var widget = new CommentList({ id: 1});
widget.update();
widget.acceptVisitor(new NotificationList());
widget.acceptVisitor(new NotificationIndicator());
}());
Back to the patterns list
Interpreter
The pattern allows:
- to define a mechanism to understand and act upon the grammar;
- decoupling grammar from underlying expressions.
PHP Example
Apparently one of the most common application for the Interpreter pattern are template engines. Here is an example for a simple web template parser.
<?php
/*
* @category Design Pattern Tutorial
* @package Interpreter Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
/**
* Context
*/
class View
{
private $_vars;
public function assign($var, $val)
{
$this->_vars[$var] = $val;
}
public function getVars()
{
return $this->_vars;
}
}
/**
* Expression
*/
interface Expression_Interface
{
public function interpret($expression, View $context);
}
class Web_Template_Interpreter implements Expression_Interface
{
public function interpret($expression, View $context)
{
$assigments = self::_getAssigmentsMap($context->getVars());
$output = strtr($expression, $assigments);
if (($errorMsg = self::_isValidXml($output)) === true) {
return $output;
} else {
return "[Error:]\n" . $errorMsg;
}
}
private static function _getAssigmentsMap($vars)
{
$keys = array_map(function ($key) { return "{{$key}}"; }, array_keys($vars));
return array_combine($keys, array_values($vars));
}
private static function _isValidXml($xml)
{
libxml_use_internal_errors(true);
$sxe = simplexml_load_string("<?xml version='1.0' standalone='yes'?>{$xml}");
if (!$sxe) {
$msgs = array_map(function ($li) { return $li->message; }, libxml_get_errors());
return implode("", $msgs);
}
return true;
}
}
/**
* Usage
*/
$view = new View();
$view->assign('title', 'Article Title');
$view->assign('text', 'Lorem ipsum dolor sit amet, consectetur adipisicing elit');
$invalidTpl = <<<'EOD'
<article>
<header>
...
EOD;
$validTpl = <<<'EOD'
<article>
<header>
<h2>{title}</h2>
</header>
<section>
{text}
</section>
</article>
EOD;
$interpreter = new Web_Template_Interpreter();
echo "Invalid template:\n", $interpreter->interpret($invalidTpl, $view), "\n---------------\n\n";
echo "Valid template:\n", $interpreter->interpret($validTpl, $view), "\n---------------\n\n";
// Output:
// Invalid template:
// [Error:]
// Premature end of data in tag header line 2
// Premature end of data in tag article line 1
//
// ---------------
//
// Valid template:
// <article>
// <header>
// <h2>Article Title</h2>
// </header>
// <section>
// Lorem ipsum dolor sit amet, consectetur adipisicing elit
// </section>
// </article>
// ---------------
JS/ES5 Example
/*
* @category Design Pattern Tutorial
* @package Interpreter Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
(function() {
var View_Template = function() {
var _template = null, _vars = [];
return {
assign: function(key, val) {
_vars[key] = val;
},
setTemplate: function (template) {
_template = template;
},
getTemplate: function () {
return _template;
},
getVars: function () {
return _vars;
}
}
}
var Web_Template_Interpreter = function(viewTemplate) {
var _viewTemplate = viewTemplate;
return {
getInterpreted: function() {
var tpl = _viewTemplate.getTemplate();
var vars = _viewTemplate.getVars();
for (var key in vars) {
var re = new RegExp("\{" + key + "\}", 'g');
tpl = tpl.replace(re, vars[key]);
};
return tpl;
}
}
}
/**
* Usage
*/
var tmp = new View_Template();
tmp.setTemplate('<article><header><h2>{title}</h2></header>'
+ '<section>{text}</section></article>');
tmp.assign('title', 'Article Title');
tmp.assign('text', 'Lorem ipsum dolor sit amet, consectetur adipisicing elit');
var interpreter = new Web_Template_Interpreter(tmp);
console.log(interpreter.getInterpreted());
// Output
// <article><header><h2>Article Title</h2></header>
// <section>Lorem ipsum dolor sit amet, consectetur adipisicing elit</section></article>
}());
Back to the patterns list
Template
The pattern allows:
- to define the framework of an algorithm, allowing implementing classes, which define the actual behavior;
- subclasses implement (through method overriding) behavior that can vary;
- to avoid duplication in the code: the general workflow structure is implemented once in the abstract class’s algorithm, and necessary variations are implemented in each of the subclasses;
- to control at what point(s) subclassing is allowed.
Template method in some ways reminds of Adapter pattern (GRASP principle). The difference is that Adapter gives the same interface for several operations while Template Method does so only for one.
PHP Example
Let’s say we have different types of feedback forms on the site. They differ by the way to generate email message body, however their headers and footers (attachment section) are generated the same way. Besides, we need centralized control over that common functionally. So, we have abstract class generalized by any of concrete feedback implementation classes. Those classes overwrite _getBody method.
<?php
/*
* @category Design Pattern Tutorial
* @package Template Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
class Mock
{
public static function mail($to, $subject, $body, $headers)
{
printf ("Mail to %s\nHeaders: %s\nSubject: %s\nBody: %s\n\n", $to, $headers, $subject, $body);
}
}
/**
* Template class
*/
abstract class FeedbackFormTemplate
{
const FEEDBACK_RECIPIENT_EMAIL = '[email protected]';
protected $_subject;
protected $_fromName;
protected $_fromEmail;
public function send()
{
$body = $this->_getBody() . $this->_getAttachment();
Mock::mail(self::FEEDBACK_RECIPIENT_EMAIL, $this->_subject, $body, $this->_getHeaders());
}
private function _getHeaders()
{
return sprintf("From: %s <%s>\r\n", $this->_fromName, $this->_fromEmail);
}
private function _getAttachment()
{
return strtr('<h2>Session Info</h1><d2>'
. '<dt>User Id:</dt><dd>{userId}</dd>'
. '<dt>User Agent:</dt><dd>{userAgent}</dd>'
. '<dt>URI:</dt><dd>{uri}</dd>'
. '</dl>', array(
'{userId}' => (isset ($_SESSION["userId"]) ? $_SESSION["userId"] : "undefined"),
'{userAgent}' => $_SERVER["HTTP_USER_AGENT"],
'{uri}' => $_SERVER["REQUEST_URI"],
));
}
abstract protected function _getBody();
}
/**
* Concrete implementations
*/
final class SupportFeedbackForm extends FeedbackFormTemplate
{
protected $_subject = 'Support Request';
private $_data;
public function __construct(SupportFeedbackFormEntity $data)
{
$this->_data = $data;
$this->_fromName = $data->name;
$this->_fromEmail = $data->email;
}
protected function _getBody()
{
return strtr('<h1>{title}</h1><dl>'
. '<dt>Name:</dt><dd>{name}</dd>'
. '<dt>Email:</dt><dd>{email}</dd>'
. '<dt>Category:</dt><dd>{city}</dd>'
. '<dt>Message:</dt><dd>{message}</dd>'
. '</dl>', array(
'{title}' => $this->_subject,
'{name}' => $this->_data->name,
'{email}' => $this->_data->email,
'{category}' => $this->_data->category,
'{message}' => $this->_data->message,
));
}
}
final class ContactFeedbackForm extends FeedbackFormTemplate
{
protected $_subject = 'Contact Form Request';
private $_data;
public function __construct(ContactFeedbackFormEntity $data)
{
$this->_data = $data;
$this->_fromName = $data->name;
$this->_fromEmail = $data->email;
}
protected function _getBody()
{
return srttr('<h1>{title}</h1><dl>'
. '<dt>Name:</dt><dd>{name}</dd>'
. '<dt>Email:</dt><dd>{email}</dd>'
. '<dt>City:</dt><dd>{city}</dd>'
. '<dt>Address:</dt><dd>{address}</dd>'
. '<dt>Message:</dt><dd>{message}</dd>'
. '</dl>', array(
'{title}' => $this->_subject,
'{name}' => $this->_data->name,
'{email}' => $this->_data->email,
'{city}' => $this->_data->city,
'{address}' => $this->_data->address,
'{message}' => $this->_data->message,
));
}
}
/**
* Data Transfer Objects
*/
abstract class FeedbackFormEntity
{
public $name;
public $email;
public $message;
}
class ContactFeedbackFormEntity extends FeedbackFormEntity
{
public $city;
public $address;
}
class SupportFeedbackFormEntity extends FeedbackFormEntity
{
public $category;
}
/**
* Usage
*/
$data = new SupportFeedbackFormEntity();
$data->name = "John Snow";
$data->email = "John Snow";
$data->category = "John Snow";
$data->message = "John Snow";
$form = new SupportFeedbackForm($data);
$form->send();
// Output
// Mail to [email protected]
// Headers: From: John Snow <John Snow>
// Subject: Support Request
// Body: <h1>Support Request</h1>..
JS/ES5 Example
Following example shows how the objects representing different types of events (MessageEvent and NotificationEvent) extending template object (EventTemplate) can be brought to the same interface for the send operation.
/*
* @category Design Pattern Tutorial
* @package Template Sample
* @author Dmitry Sheiko <[email protected]>
* @link http://dsheiko.com
*/
(function() {
var Channel = new WebSocket("ws://site.com/demo");
/**
* Template object
*/
var EventTemplate = function() {}
EventTemplate.prototype = {
eventType : null,
send : function(senderClientId, addresseeClientIds) {
Channel.send({
eventType : this.eventType,
senderClientId : senderClientId,
addresseeClientIds : addresseeClientIds,
payload : this.getPayload()
});
}
}
/**
* Concrete implementations
*/
var NotificationEvent = function(notificationType) {
// Validate input arguments
this.eventType = 'Notification';
this.notificationType = notificationType;
}
NotificationEvent.prototype = new EventTemplate();
NotificationEvent.prototype.notificationType = null;
NotificationEvent.prototype.getPayload = function() {
return {
notificationType : this.notificationType
}
}
var MessageEvent = function(messageText) {
// Validate input arguments
this.eventType = 'Message';
this.messageText = messageText;
}
MessageEvent.prototype = new EventTemplate();
MessageEvent.prototype.messageText = null;
MessageEvent.prototype.getPayload = function() {
return {
messageText : this.messageText
}
}
/**
* Usage
*/
var event = new NotificationEvent('Like');
event.send(1, [2]);
var event = new MessageEvent('Text');
event.send(1, [2]);
})();
Back to the patterns list