Skip to main content
a z C o d e l a n d

Factory Design Pattern

Created: July 20th, 2024

Updated: July 22nd, 2024

Explanation

The Factory Design Pattern is a creational design pattern that encapsulates logic for instantiating instances of various classes. However, the pattern does not encapsulate the logic for initializing the state and/or behavior of those instances; those implementation details are delegated to the classes of the instances themselves. Therefore, the pattern doesn’t know the inner workings of the classes it instantiates instances of; it’s decoupled from those classes.

Wikipedia article says:

In object-oriented programming, a factory is an object for creating other objects; formally, it is a function or method that returns objects of a varying prototype or class from some method call, …

Analogy

We can think of a logistics broker as the factory in the Factory Design Pattern. This broker might provide services to move items from point A to point B using a variety of different types of transportation, such as land, air, sea, rail, etc. The broker delegates the transportation requests to various logistic companies. These logistic companies can be thought of as the subclasses in the pattern, each handling the actual implementation details of a type of transportation.

Code Example

ES6 Syntax

  class LogisticsBroker {
    createTransportationService(request) {
      switch (request.type) {
        case 'land':
          return new LandTransportationService(request);
        case 'air':
          return new AirTransportationService(request);
        case 'sea':
          return new SeaTransportationService(request);
        case 'rail':
          return new RailTransportationService(request);
        default:
          throw new Error('Invalid transportation type');
      }
    }
  }

  class LandTransportationService { 
    constructor(request) {
      this.request = request;
    }
    calculateCost(from, to) { /* implementation */ }
    schedueTransportation(from, to) { /* implementation */ } 
    trackTransportationStatus(){ /* implementation */ }
  }
  class AirTransportationService { /* implementation */ }
  class SeaTransportationService { /* implementation */ }
  class RailTransportationService {  /* implementation */ }

  // usage
  const request = {
    type: 'land',
    fromAddress: '123 Main St Ottawa, ON',
    toAddress: '123 Main St Toronto, ON',
    date: '2024-07-20',
    items: [ /* ... */]
  };

  const broker = new LogisticsBroker();
  const service = broker.createTransportationService(request);

The advantages of the factory design pattern can be seen here. The LogisticsBroker class simplifies the client’s interaction by abstracting away all the details and making broker.createTransportationService(request) method the only interface available to request a transportation service.

This abstraction also provides the logisticsBroker class flexibility. Its implementation details or the types of transportation services it provides can be added to or removed, but as long as it provides the broker.createTransportationService(request) method as an interface, the client’s usage of the class won’t be impacted.

Also, since the implementation details of the other transportation services classes are encapsulated within their respective classes and therefore abstracted away from the logisticsBroker, this decoupling simplifies the overall code, which makes it easier to maintain and modify different sections without impacting the rest of the code.

Factory Pattern Before ES6

Prior to ES6, the class syntax that we used above did not exist, so factory functions were used instead, which are regular functions that return an object. The code example below re-implements the logisticsBroker class, but using ES5 syntax.


ES5 Syntax

  function logisticsBroker() {
    return {
      createTransportationService: function (request) {
        switch (request.type) {
          case 'land':
            return landTransportationService(request);
          case 'air':
            return airTransportationService(request);
          case 'sea':
            return seaTransportationService(request);
          case 'rail':
            return railTransportationService(request);
          default:
            throw Error('Invalid transportation type');
        }
      }
    };
  };

  function landTransportationService(request) {
    return {
      calculateCost: function (from, to) {},
      schedueTransportation: function (from, to) {},
      trackTransportationStatus: function () {},
    };
  }


  function airTransportationService(request) { return {}; }
  function seaTransportationService(request) { return {}; }
  function railTransportationService(request) { return {}; }

  // usage
  var request = {
    type: 'land',
    fromAddress: '123 Main St Ottawa, ON',
    toAddress: '123 Main St Toronto, ON',
    date: '2024-07-20',
    items: [ /* */]
  };

  var broker = logisticsBroker();
  var service = broker.createTransportationService(request);

However, this implementation isn’t memory efficient as the methods get defined for each object created from the factory function. At first, I thought we could just define the methods on the prototype of the factory function, but that doesn’t work because the objects returned from the factory function are not instances of the factory function and therefore don’t inherit from the prototype of the factory function. However, there is a solution, we can define an object that contains all the methods for each factory function, and then, within each factory function, we can create an object using Object.create(proto) and pass the object containing the methods as the proto argument. This will set up the prototypal inheritance chain for the newly created object by setting its internal [[Prototype]] property to the proto argument.

Here’s the code example:


ES5 Syntax

  var logisticsBrokerMethods = {
    createTransportationService: function (request) {
      switch (request.type) {
        case 'land':
          return landTransportationService(request);
        case 'air':
          return airTransportationService(request);
        case 'sea':
          return seaTransportationService(request);
        case 'rail':
          return railTransportationService(request);
        default:
          throw Error('Invalid transportation type');
      }
    }
  };

  var landTransportationServiceMethods = {
    calculateCost: function (from, to) { /* implementation */ },
    scheduleTransportation: function (from, to) { /* implementation */ },
    trackTransportationStatus: function () { /* implementation */ },
  };

  var airTransportationServiceMethods = { /* implementation */ };
  var seaTransportationServiceMethods = { /* implementation */ };
  var railTransportationServiceMethods = { /* implementation */ };

  function logisticsBroker() {
    return Object.create(logisticsBrokerMethods);
  };

  function landTransportationService(request) {
    var service = Object.create(landTransportationServiceMethods);
    service.request = request;
    return service;
  }

  function airTransportationService(request) {
    var service = Object.create(airTransportationServiceMethods);
    service.request = request;
    return service;
  }

  function seaTransportationService(request) {
    var service = Object.create(seaTransportationServiceMethods);
    service.request = request;
    return service;
  }

  function railTransportationService(request) {
    // we can also use a data descriptor to define the request property
    return Object.create(railTransportationServiceMethods, {
      request: {
        value: request,
        writable: true,
        enumerable: true,
        configurable: true
      }
    });
  };

  // usage
  // same as before 

This implementation is memory efficient, but it brings about a behavior that is important to be aware of: any property or function defined within a factory function's scope is not accessible by methods defined on the prototype of the service object. For example, the newly created service object within the landTransportationService factory function inherits all the methods from the proto object passed to the Object.create(proto). All those inherited methods do not have access to any property or function defined witin the scope of the landTransportationService factory function, because those properties or functions become “private” to that factory function. However, methods and functions defined within the factory function scope can access those properties because they were either defined in the same top level scope or have access due to closures.

I’ve included a section on this behavior in my Constructor Design Pattern post, please check out the section titled Understanding Accessing Function Scope Variables in Prototype Methods .


Whether we use this variation or the previous one, factory functions grant us the ability to have a private interface where we can define private properties or private functions. If you’re interested, I’ve covered private and public interfaces, which come about due to closures, in my Module Design Pattern article .

Wrapping Up

At the beginning of this article, I was a little confused as to what really differentiated the factory design pattern from the constructor design pattern , because the ES5 example above can be implemented using the constructor pattern, but at that point, it can’t be considered a factory pattern. Therefore, I’ve learned that the moment we use the new operator, the ES5 example, with slight modifications, is considered a constructor pattern.

Through that insight, it now makes sense to me why the instanceof operator returns false for objects created from factory functions, because factory functions return objects, not instances. Instances are only created when we use the new operator with a constructor function or class. This explains why those objects inherit from Object.prototype, unless the prototype has been explitcly set to another object, and not from the factory function’s prototype object.

The comparison has also highlighted the simplicity of creating closures using factory functions as an advantage over constructor functions. Although, It’s not too difficult with constructor functions either, but it’s not as clean as the factory function. But my main insight has been that for both design patterns, there is a trade-off to be made between memory efficiency and access to private data.


I really like the factory design pattern!

I hope you’ve found this article helpful. Thank you for reading!


© 2024 NazCodeland