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

Constructor Design Pattern

Created: June 30th, 2024

Updated: July 22nd, 2024

Explanation

The Constructor Design Pattern is a creational design pattern and it is automatically invoked every time we instantiate a new instance of a class using the new operator. This method is responsible for initializing the state of the newly created instance by assigning its initial properties and methods. If the class is a subclass, meaning it inherits from a superclass, the constructor invokes the superclass’s constructor to ensure newly created instances are assigned the properties and methods of the superclass.

Wikipedia article says:

In class-based, object-oriented programming, a constructor (abbreviation: ctor) is a special type of function called to create an object. It prepares the new object for use, often accepting arguments that the constructor uses to set required member variables.

Analogy

We can think of a bicycle factory as an analogy for the constructor design pattern. A bicycle factory probably uses a blueprint to ensure all bicycles have the same components, such as tires, chains, and a gear shifting mechanism. This is similar to how a constructor method assigns initial properties and methods to new instances. Just as the factory may produce some parts in-house and purchase others, a subclass might initialize some properties and methods and invoke the superclass’s constructor for inheriting properties and methods.

Code Example

ES6 Syntax

  class Bicycle { 	
    // Class fields are instance specific properties or methods
    model = 'regular bike';
    chain = 'standard chain';
    year = 2024;
    tires = 2;

    constructor(color, size) { 		
      // Properties defined in the constructor are instance specific
      this.color = color;
      this.size = size;  
      // See 'Wrapping Up' for more on defining methods in the constructor
      this.someMethod = function () {
        // Do something
      };
    }

    honk() {
      console.log('honk');
    }   
    gearShiftingMechanism() {
    	// Regular bike implementation
    }
  }

  // Instantiate a new instance of Bicycle class
  const bicycle = new Bicycle('blue', 'large');

MountainBike is a subclass of Bicycle and therefore it needs to invoke the constructor of its superclass by using the super keyword and passing in the required arguments.

  class MountainBike extends Bicycle { 	
    model = 'mountain bike';
    chain = 'advanced chain';
    terrainTypes = ['trail', 'downhill', 'cross-country'];

    constructor(color, size, terrainType) { 		
      // super needs to be invoked before using `this` keyword
      super(color, size);      
      if (!this.terrainTypes.includes(terrainType)) {
        throw new Error('Invalid terrain type');
      } else {
        this.terrainType = terrainType;
      }
    } 			

    // Override the method defined in the superclass by
    // Adding a specific implementation for mountain bikes
    gearShiftingMechanism() {
   	  // Mountain bike implementation
    }
  } 

  // Instantiate a new instance of Bicycle class
  const mountainBike = new MountainBike('black', 'medium', 'trail'); 	

Constructors Before ES6

Prior to ES6, the class syntax that we used above did not exist. The keywords class, extends, super, and the constructor method were not a thing. Instead, constructor functions, which by convention should start with a capital letter, were used to create objects (instances), and the inheritance chain was setup by assigning the prototype of the constructor function to an object created from the prototype of another constructor function. This allowed failed lookup calls on instance methods or properties to be delegated to the prototype of the parent constructor function. To share methods between all instances of a constructor function, the method had to be defined on the prototype of the constructor function. Here is an example of the Bicycle and MountainBike classes using constructor functions:


ES5 Syntax

  function Bicycle(color, size){ 	
    // Equivalent to instance specific properties
    this.modal = 'regular bike';
    this.chain = 'standard chain';
    this.year = 2024;
    this.tires = 2;
    this.color = color;
    this.size = size;
  }

  Bicycle.prototype.honk = function(){
    console.log('honk');
  }

  Bicycle.prototype.gearShiftingMechanism = function(){}

  var bicycle = new Bicycle('blue', 'large');
  function MountainBike(color, size, terrainType){ 
    this.model = 'mountain bike';
    this.chain = 'advanced chain';
    this.terrainTypes = ['trail', 'downhill', 'cross-country'];
  
    // Equivalent to super(color, size);
    Bicycle.call(this, color, size)
  }

  // Setup the inheritance chain
  MountainBike.prototype = Object.create(Bicycle.prototype);

  // Reset the constructor property
  // Resetting the constructor is important for correctly 
  // identifying the constructor of an instance and for other reasons.
  MountainBike.prototype.constructor = MountainBike

  // Steps for defining methods on the prototype of MountainBike:
  // 1. Assign the prototype of MountainBike to a new object created
  //  from the Bicycle.prototype object.
  // 2. Reset the constructor property to point back to MountainBike.
  // 3. Define methods on the prototype of MountainBike.
  // Otherwise the methods/properties will be lost.
  MountainBike.prototype.gearShiftingMechanism = function(){
    // This method will override the method inherited from
    // the Bicycle prototype
  }

  var mountainBike = new MountainBike('black', 'medium', 'trail'); 	

Understanding Accessing Function Scope Variables in Prototype Methods

The # symbol in ES6 is used to define private class fields, which are instance-specific properties or methods. These class fields can only be accessed from within the class where they’re defined; otherwise, an error will be thrown if they are accessed from outside the class.

In both ES5 and ES6, to achieve data privacy in any type of function, such as a constructor function, we can define the property using var, const or let instead of on the this keyword and access it through the use of closures . However, this solution leads to the problem where methods defined on the prototype of the constructor function cannot access that private property. It’s easier to show this behavior with a code example:


ES6 Syntax

  class Bicycle {
    #serialNumber = 123; 	
    constructor(color, size) {
      this.color = color;
      this.size = size;
    }

    logSerialNumber() { console.log(this.#serialNumber); } 
  }

  const bicycle = new Bicycle('blue', 'large'); 
  console.log(bicycle.#serialNumber); // output: SyntaxError // 
  bicycle.logSerialNumber(); // output: 123 // 

Everything works as expected; the private class field #serialNumber is not accessible from outside the class, but the method logSerialNumber is able to access the private class field.



ES5 Syntax

  function Bicycle(color, size) {
    var serialNumber = 123; 	
    this.color = color;
    this.size = size;
  }

  Bicycle.prototype.logSerialNumber = function () { 
    // will throw 'ReferenceError: serialNumber is not defined'
    console.log(serialNumber);
  };

  var bicycle = new Bicycle('blue', 'large'); 
  console.log(bicycle.serialNumber); // output: undefined
  bicycle.logSerialNumber(); // output: the above error

So, since we want to keep serialNumber private, we’ll define it using const instead of on the this keyword because the ‘this’ keyword represents the instance returned by the constructor function, and therefore, if it was defined on the instance, it can be accessed by doing this.serialNumber and so it wouldn’t be private. But this privacy means that methods defined on the prototype of the constructor function won’t be able to access ‘serialNumber’ as it is defined within the constructor function’s scope and not on the instance itself.

However, methods and functions defined within the scope of the constructor function can access ‘serialNumber’ because they were either defined in the same top level scope or have access due to closures.


  function Bicycle(color, size) {
    const serialNumber = 123;
    this.color = color;
    this.size = size;
    this.logSerialNumber = function () { log(); };
    function log() { console.log(serialNumber); }
  }

  var bicycle = new Bicycle('blue', 'large');

  console.log(bicycle.serialNumber); // output: undefined
  bicycle.logSerialNumber(); // output: 123

So, this leads to the realization that there is a trade-off to be made between memory efficiency and access to private data.

  • If the objective is to minimize memory usage, then methods should be defined on the prototype of the constructor function, as these methods are shared between all instances. However, these methods will not have access to properties or functions that are defined within the scope of the constructor function, as they are considered “private” to the constructor function.

  • If the objective is to have access to private data, then we should define methods within the constructor function. These methods will have access to variables or functions defined within the constructor function because they were either defined in the same top level scope or have access due to closures. However, this results in using more memory as each instance will have its own copy of these methods.

Wrapping Up

Some sources do not view the constructor as a design pattern but rather as a method of a class for initializing properties and methods of a new instance. Also, many definitions, just like mine, say that it is responsible for initializing properties and methods. But I have not come across a use case for initializing methods within the constructor, and if we were to initialize a method within the constructor, it would be unique for each instance and therefore not a method in the sense of what a method means in OOP: a function on a class that is shared between all instances of that class.

But, as my mentor Chase pointed out, MDN says, A property's value can be a function, in which case the property is known as a method. He also pointed out that we developers only write the constructor method, but behind the scenes, there is probably a lot more going on in the construction phase. So, therefore, there probably is a use case for initializing methods within the constructor that I just don’t know about due to my lack of knowledge.

I’ve also asked Jonny Magrippis , who is a great instructor, and even he says he hasn’t seen a legit use case for it. If you do know of a use case, please share!


© 2024 NazCodeland