Abstract Factory

Provide an interface for creating families of related or dependent objects without specifying their concrete classes.



Usage in JavaScript:
medium


Introduction


An Abstract Factory creates objects that are related by a common theme. In object-oriented programming a Factory is an object that creates other objects. An Abstract Factory has abstracted out a theme which is shared by the newly created objects.

Suppose we have two Abstract Factories whose task it is to create page controls, such as, buttons, textboxes, radio buttons, and listboxes. One is the Light Factory which creates controls that are white and the other the Dark Factory which creates controls that are black. Both Factories creates the same types of controls, but they differ in color, which is their common theme. This is an implementation of the Abstract Factory pattern.

Over time the Abstract Factory and Factory Method patterns have merged into a more general pattern called Factory. A Factory is simply an object that creates other objects.

You may be wondering why you would want to leave the responsibility of the construction of objects to others rather than simply calling a constructor function with the new keyword directly. The reason is that that constructor functions are limited in their control over the overall creation process and sometimes you will need to hand over control to a factory that has broader knowledge.

This includes scenarios in which the creation process involves object caching, sharing or re-using of objects, complex logic, or applications that maintain object and type counts, and objects that interact with different resources or devices. If your application needs more control over the object creation process, consider using a Factory.


Diagram



Participants


The objects participating in this pattern are:

  • AbstractFactory -- not used in JavaScript
    • declares an interface for creating products
  • ConcreteFactory -- In sample code: EmployeeFactory, VendorFactory
    • a factory object that 'manufactures' new products
    • the create() method returns new products
  • Products -- In sample code: Employee, Vendor
    • the product instances being created by the factory
  • AbstractProduct -- not used in JavaScript
    • declares an interface for the products that are being created

JavaScript Code


JavaScript does not support class-based inheritance therefore the abstract classes as depicted in the diagram are not used in the JavaScript sample. Abstract classes and interfaces enforce consistent interfaces in derived classes. In JavaScript we must ensure this consistency ourselves by making sure that each 'Concrete' object has the same interface definition (i.e. properties and methods) as the others

In the example we have two Concrete Factories: EmployeeFactory and VendorFactory. The first one creates Employee instances, the second one Vendor instances. Both products are person types (with the same interface) which allows the client to treat them the same. An array with two employees and two vendors is created. Each person is then asked to say what and who they are.

The log function is a helper which collects and displays results.


function Employee(name) {
    this.name = name;
    this.say = function () {
        log.add("I am employee " + name);
    };
}

function EmployeeFactory() {
    this.create = function(name) {
        return new Employee(name);
    };
}

function Vendor(name) {
    this.name = name;
    this.say = function () {
        log.add("I am vendor " + name);
    };
}

function VendorFactory() {
    this.create = function(name) {
        return new Vendor(name);
    };
}

// log helper
var log = (function () {
    var log = "";
    return {
        add: function (msg) { log += msg + "\n"; },
        show: function () { alert(log); log = ""; }
    }
})();


function run() {

    var persons = [];

    var employeeFactory = new EmployeeFactory();
    var vendorFactory = new VendorFactory();

    persons.push(employeeFactory.create("Joan DiSilva"));
    persons.push(employeeFactory.create("Tim O'Neill"));

    persons.push(vendorFactory.create("Gerald Watson"));
    persons.push(vendorFactory.create("Nicole McNight"));

    for (var i = 0, len = persons.length; i < len; i++) {
        persons[i].say();
    }

    log.show();
}
Run



JavaScript Optimized Code


The Namespace pattern has applied to keep the code out of the global namespace. Our namespace is named Patterns.Classic. A Revealing Module named AbstractFactory encapsulates all of Abstract Factory's functions. Only the EmployeeFactory and VendorFactory are exposed (revealed), whereas the Employee and Vendor constructor functions are kept private inside the module.

The Patterns object contains the namespace function which constructs namespaces non-destructively, that is, if a name already exists it won't overwrite it.

The log function is a helper which collects and displays results.


var Patterns = {
    namespace: function (name) {
        var parts = name.split(".");
        var ns = this;

        for (var i = 0, len = parts.length; i < len; i++) {
            ns[parts[i]] = ns[parts[i]] || {};
            ns = ns[parts[i]];
        }

        return ns;
    }
};

Patterns.namespace("Classic").AbstractFactory = (function () {

    var Employee = function (name) {
        this.name = name;
        this.say = function () {
            log.add("I am employee " + name);
        };
    };

    var Vendor = function (name) {
        this.name = name;
        this.say = function () {
            log.add("I am vendor " + name);
        };
    };

    var EmployeeFactory = function () {
        this.create = function (name) {
            return new Employee(name);
        };
    };

    var VendorFactory = function () {
        this.create = function (name) {
            return new Vendor(name);
        };
    };

    return {
        EmployeeFactory: EmployeeFactory, 
        VendorFactory: VendorFactory
    };
})();

// log helper
var log = (function () {
    var log = "";
    return {
        add: function (msg) { log += msg + "\n"; },
        show: function () { alert(log); log = ""; }
    }
})();


function run() {

    var abstract = Patterns.Classic.AbstractFactory;

    var employeeFactory = new abstract.EmployeeFactory();
    var vendorFactory = new abstract.VendorFactory();

    var persons = [];

    persons.push(employeeFactory.create("Joan DiSilva"));
    persons.push(employeeFactory.create("Tim O'Neill"));

    persons.push(vendorFactory.create("Gerald Watson"));
    persons.push(vendorFactory.create("Nicole McNight"));

    for (var i = 0, len = persons.length; i < len; i++) {
        persons[i].say();
    }

    log.show();
}
Run



Builder