Abstract Factory Pattern

The pattern allows:

  • run-time control over object creation process;
  • to apply Dependency Injections;
  • to use families of objects together;
  • to decouple concrete class from the clients.

Abstract Factory Pattern


PHP Example

Do you base your application design on MVC paradigm? It’s likely, you do. If it happened to you to create instances of model classes directly or, worse, retrieve them via Singleton, you probably know of the problem it brings. When you test a model, which depends on other models, it would be not so easy to substitute dependent ones by mock objects. The same if you want to switch your model to a remote API. Using abstract factory makes it a way easier. As you see here below, abstract factory uses specified model factory, which essentially creates the requested object. If we need other family of model classes, we just let know about to the abstract factory.

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

class Lib_Factory_Exception extends Exception
{
}
/**
 * Abstract factory
 */
class Lib_Factory
{
    private static $modelFactory = 'Model_Factory';
    public static function setModelFactory($model)
    {
        self::$modelFactory = $model;
    }
    public static function model($model)
    {
        if (!class_exists(self::$modelFactory)) {
            throw new Lib_Factory_Exception('Requested factory ' . MODEL_FACTORY . ' not found');
        }
        $className = self::$modelFactory;
        return $className::getInstance($model);
    }
}
/**
 * Concrete factory
 */
interface Model_Factory_Interface
{
    public static function getInstance($model);
}
class Model_Factory implements Model_Factory_Interface
{
    const CLASS_PREF = 'Model_';
    public static function getInstance($model)
    {
        $className = self::CLASS_PREF . $model;
        return new $className();
    }
}
class Model_Mock_Factory implements Model_Factory_Interface
{
    const CLASS_PREF = 'Model_Mock_';
    public static function getInstance($model)
    {
        $className = self::CLASS_PREF . $model;
        return new $className();
    }
}

interface Model_User_Interface
{
    public function get();
}
class Model_User implements Model_User_Interface
{
    public function get()
    {
        return "Real data\n";
    }
}
class Model_Mock_User implements Model_User_Interface
{
    public function get()
    {
        return "Mock data\n";
    }
}

/**
 * Usage
 */
echo "Pass 1:\n", Lib_Factory::model('User')->get();
Lib_Factory::setModelFactory('Model_Mock_Factory');
echo "Pass 2:\n", Lib_Factory::model('User')->get();

// Output:
// Pass 1:
// Real data
// Pass 2:
// Mock data

JS/ES5 Example

In the previous example we established control over the family of the model classes. I decided for JavaScript would be more common to use Widget classes family.

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

(function() {

/**
 * Concrete factories
 */
var MockWidgetFactory = function(widget) {
    var CLASS_PREF = 'MockWidget_';
    return new this[CLASS_PREF + widget]();
};

var WidgetFactory = function(widget) {
    var CLASS_PREF = 'Widget_';
    return new this[CLASS_PREF + widget]();
};

/**
 * Abstract factory
 */
var Factory = (function() {
    var _widgetFactory = WidgetFactory;
    return {
        setFactory: function(factory) {
            _widgetFactory = factory;
        },
        widget: function(widget) {
            var instance = _widgetFactory(widget);
            instance.init();
            return instance;
        }
    }
}());

Widget_Slideshow = function() {
    return {
        init: function() {
            console.log('Widget slideshow initialized');
        }
    }
};
MockWidget_Slideshow = function() {
    return {
        init: function() {
            console.log('Mock widget slideshow initialized');
        }
    }
};

/**
 * Usage
 */
var widget = Factory.widget('Slideshow');
Factory.setFactory(MockWidgetFactory);
var widget = Factory.widget('Slideshow');

// Output
// Widget slideshow initialized
// Mock widget slideshow initialized

}());