Design Patterns - The Factory Pattern in JavaScript

TL; DR

Source code can be found on GitHub, jsFactory repo


Design Patterns - The Factory

Table of Contents

  1. Design Patterns
  2. The Factory Pattern
  3. Factory Pattern in JS
  4. Simple Factory
  5. Configurable Factory
  6. Demo

Design Patterns ^

A design pattern is the re-usable form of a solution to a design problem. 1

In 1977 Christopher Alexander, at that time a well-known architect, published a book, titled A Pattern Language. He explained his ideas about incremental and organic design and laid the foundations of Design Patterns.

In 1994 was released the Design Pattern book, the book which is the symbol of the Design Pattern topic, the classic written by the Gang of Four: Design Patterns: Elements of Reusable Object-Oriented Software. All the examples in this book were written in C++ and presented using C++’s language features. Understanding this book and the examples in this book is not a trivial task.

You may ask why do we need design patterns? The answer is simple, first of all design patterns offer solutions to problems which might appear in our apps. Of course, there are multiple solutions for these issues... BUT the solutions which design patterns offer have been proven and tested by thousands of engineers so we can rely on these.

The Factory Pattern ^

The Factory design pattern is one of the most simple design patterns. The main idea of this pattern is to have a common place (function, object, class, module) which is used to initialize (create) objects within the application. Imagine, if you want to update an existing type/object, lets say you want to add two more properties to an Employee type. This Employee type is used all over the modules inside your app (login, products and services, salary module, employee tracking, career management and so on).

Just imagine if the creation of objects is spread over hundreds of files within an application, maintaining and handling that large amount of files is very error-prone when the object creation logic needs to be updated. Missing to adjust some of the places where the objects are created can cause misbehavior, crash, data corruption in the application.

If the creation of the objects (in our case of type Employee but not only) is not controlled by a single component (a factory) then, you would need to change the code everywhere in the application where a new Employee is created. Once the creation of the objects is centralized, the number of affected code files, classes, modules is much smaller when this type of change is needed.

The Factory design pattern is a creational design pattern.

Creational Design patterns are the ones which usually everyone knows about, mostly because during development most of us faced situations when there was need to create multiple objects and everyone knew (or felt) these objects should be created in the same place or at least in the same manner. Some of the commonly known design patterns are Factory, Abstract Factory, Builder and of course Singleton.

Factory Pattern in JS ^

There are multiple ways to implement the Factory pattern within JavaScript, I implemented two factories, SimpleFactory and ConfigurableFactory. Both of these will serve the same purpose, creating different type of Employee objects.

Simple Factory ^

The SimpleFactory is a node module. Inside the constructor function, I defined three methods getSalesEmployee, getEngineerEmployee, getName. The first two return JSON objects with different properties (based on the type of Employee), the last returns the name of the factory object.

function SimpleFactory(name) {  
    var factoryName = name;

    var getSalesEmployee = function(firstName, lastName) {
        return {
            firstName: firstName,
            lastName: lastName,
            comission: 0,
            salary: 100,
            projects: [],
            type: 'sales'
        };
    }

    var getEngineerEmployee = function(firstName, lastName) {
        return {
            firstName: firstName,
            lastName: lastName,
            salary: 150,
            manager: '',
            technologies: [],
            projects: [],
            type: 'engineer'
        }
    }

    var getName = function() {
        return factoryName;
    }

    return {
        getSales: getSalesEmployee,
        getEngineer: getEngineerEmployee,
        getName: getName
    }
}

module.exports = {  
    getInstance: SimpleFactory
}
Configurable Factory ^

The ConfigurableFactory can be used in a different way, since this can be extended with object creation logic dynamically and the implementation of the factory does not need to be changed.

function ConfigurableFactory(name) {  
    var typeMapper = {};
    var factoryName = name;

    var addTypeSupport = function(typeName, buildFunc) {
        if (!typeName || (!buildFunc && typeof(buildFunc) !== 'function')) {
            throw Error('typeName parameter needs to be defined and buildFunc paramter has to be a function');
        }
        var lcTypeName = typeName.toLowerCase();
        if (typeMapper[lcTypeName]) {
            throw Error('There is already a [' + typeName + '] type defined');
        }
        typeMapper[lcTypeName] = buildFunc;
    }

    var getObject = function(typeName) {
        var lcTypeName = typeName.toLowerCase();
        var builder = typeMapper[lcTypeName];

        if (builder !== undefined) {
            return builder();
        }

        throw Error('Cannot build type:[' + typeName + '], does not know how to...');
    }

    var getName = function() {
        return factoryName;
    }

    return {
        addTypeSupport: addTypeSupport,
        get: getObject,
        getName: getName
    }
}

module.exports = {  
    getInstance: ConfigurableFactory
}

The implementation is more complex than in the case of SimpleFactory. In this case there are also three methods, addTypeSupport, getObject, getName, but the logic within addTypeSupport and getObject is more complex.

When addTypeSupport is invoked a new property is added to the typeMapper object, the property name is the typeName parameter and the value is the buildFunc parameter.

The buildFunc parameter has to be a function, this contains the object creation logic for the type.

Demo ^
How to use SimpleFactory?

In FactoryDemo there is the demoSimpleFactory function, where I created janeDoe and billDoe using the getSales and getEngineer functions.

demoSimpleFactory: function() {  
    var simpleFactory = SimpleFactory.getInstance('SimpleEmployeeFactory');

    console.log(SEPARATOR);
    console.log(HEADER_PREFIX, 'SimpleFactory sample')
    console.log(SEPARATOR);

    console.log('The factory is called: [' + simpleFactory.getName() + ']');

    console.log('We have a new sales colleague:');
    var janeDoe = simpleFactory.getSales('Jane', 'Doe');
    console.log(JSON.stringify(janeDoe, null, 2));

    console.log();
    console.log('We have a new engineer colleague:');
    var billDoe = simpleFactory.getEngineer('Bill', 'Doe');
    console.log(JSON.stringify(billDoe, null, 2));
}

The console output of this demo code is:

========================================================
           SimpleFactory sample
========================================================
The factory is called: [SimpleEmployeeFactory]  
We have a new sales colleague:  
{
  "firstName": "Jane",
  "lastName": "Doe",
  "comission": 0,
  "salary": 100,
  "projects": [],
  "type": "sales"
}

We have a new engineer colleague:  
{
  "firstName": "Bill",
  "lastName": "Doe",
  "salary": 150,
  "manager": "",
  "technologies": [],
  "projects": [],
  "type": "engineer"
}
How to use ConfigurableFactory?

In the same FactoryDemo file, there is the demoConfigurableFactory function:

demoConfigurableFactory: function() {  
    console.log(SEPARATOR);
    console.log(HEADER_PREFIX, 'ConfigurableFactory sample')
    console.log(SEPARATOR);

    var employeeFactory = ConfigurableFactory.getInstance('EmployeeFactory');

    employeeFactory.addTypeSupport('sales', function createSales() {
        return {
            firstName: '',
            lastName: '',
            comission: 0,
            projects: [],
            type: 'sales'
        };
    });

    employeeFactory.addTypeSupport('engineer', function createEngineer() {
        return {
            firstName: '',
            lastName: '',
            salary: 150,
            manager: '',
            technologies: [],
            projects: [],
            type: 'engineer'
        };
    });

    console.log('The factory is called: [' + employeeFactory.getName() + ']');

    var johnDoe = employeeFactory.get('sales');
    console.log('John Doe is:');
    console.log(JSON.stringify(johnDoe, null, 2));

    try {
        console.log('Trying to build CEO...');
        var dannyDoe = employeeFactory.get('ceo');
    } catch (e) {
        console.error(e.message);
    }
}

I used the addTypeSupport function to extend the factory's functionality so this can create sales and engineer employees.

The console content after running the demoConfigurableFactory is:

========================================================
           ConfigurableFactory sample
========================================================
The factory is called: [EmployeeFactory]  
John Doe is:  
{
  "firstName": "",
  "lastName": "",
  "comission": 0,
  "projects": [],
  "type": "sales"
}
Trying to build CEO...  
Cannot build type:[ceo], does not know how to...  

I hope you enjoyed this blog post, if you think this has useful information and it can help others to learn, please share it.

If you want to get notifications about my upcoming articles please follow me on @gergob.