Design Patterns in Javascript

Email

Design patterns have been around for ages while developing a new project having a clear idea of what design pattern to use is very essential as a wrong design pattern can lead to messy code which has to be refactored again and again.

Our first question will be Why do we need patterns? The blunt answer is we don't want to reinvent the wheel! Problems that occur frequently enough in tech life usually have well-defined solutions, which are flexible, modular, and more understandable. These solutions when abstracted away from the tactical details become design patterns, In this article, we will start with a description of design patterns and move on to the classification of different types of design patterns with examples.

So let's dive in -

A design pattern is a description or template for how to solve a problem that can be used in many different situations. These are formalized best practices that the programmer can use to solve common problems when designing an application or system. It’s broadly classified into three types

  1. Creational Design Patterns: Creational design patterns relate to how objects are constructed from classes. As the name suggests, these patterns are for handling object creational mechanisms. A creational design pattern basically solves a problem by controlling the creation process of an object. We will discuss the following patterns in detail: Builder Pattern, Constructor Pattern, Factory Pattern, Prototype Pattern, and Singleton Pattern.

  2. Structural Design Pattern: Structural design patterns are concerned with how classes and objects can be composed, to form larger structures. These patterns focus on, how the classes inherit from each other and how they are composed of other classes. We will discuss the following patterns in detail: Adapter Pattern, Composite Pattern, Decorator Pattern, Façade Pattern, Flyweight Pattern, and Proxy Pattern.

  3. Behavioral Design Patterns: These patterns are concerned with improving communication between dissimilar objects. We will discuss the following patterns in detail: Chain of Responsibility Pattern, Command Pattern, Iterator Pattern, Mediator Pattern, Observer Pattern, State Pattern, Strategy Pattern, and Template Pattern.

(Note: In this article we will talk about design patterns that are currently being used in javascript application, so we will be ignoring some classic pattern as well as will be including some modern pattern like module and revealing module pattern. Stay tuned for the same. The example is kept simple so they are not optimized implementation of the design pattern)

Creational Design Patterns :

  • Builder Pattern

    Builder Design Pattern is a pattern used to help construct complex objects. It helps separate object construction from its representation which will help us reuse this to create different representations.Is done in the following step:

    1. A base function that will contain all the code for object creation

    2. a builder class which will be responsible for defining the steps to construct the complex objects.

    3. an optional director-class it is involved in defining methods ensuring that steps are executed in a specific order to build the object

    This would be clear with the help of an example

    Suppose there is a class Animal that contains certain properties like species, wild, etc and now we want to construct a different object with this class Animal. (Just a note I am using a class here but you can use function also.)

    class Animal {
      constructor(type, species, name, wild, food) {
        this.type = type;
        this.species = species;
        this.name = name;
        this.wild = wild;
        this.food = food;
      }
      eat(target) {
        console.log(`Eating target: ${target.name}`)
      }
    }

    if we want to instantiate a lion using this class we will do

     const rabbit = new Animal('rabbit', 'land', 'momo', null,'herbivore');

    Other than the issue that it looks confusing another problem with this pattern is that once constructors are 4 or 5 parameters long it becomes difficult to remember the required order of the parameters So to avoid that we use a builder class

    class AnimalBuilder {
      constructor(species, type) {
        this.species = species;
        this.type = type;
      }
      setName(name) {
        this.name = name;
        return this;
      }
      isWild(wild) {
        this.wild = wild;
        return this;
      }
      eats(food) {
        this.food = food;
        return this;
      }
      build() {
        if (!this.name) {
          throw new Error('Name is missing');
        }
        return new Animal(this.type, this.species, this.name, this.wild, this.food);
      }
    }

    It will be instantiated like this:

    const lion = new AnimalBuilder('lion','land')
     	.setName('lion')
    	.isWild( true)
    	.eats('carnivore')
    	.build();

    And just like that, we end up with the same result only now it's more manageable and readable to us humans!

  • Constructor Pattern

    This is a class-based creational design pattern. Constructors are special functions that can be used to instantiate new objects with methods and properties defined by that function. In this pattern by simply prefixing a call to a function with the keyword 'new', it will instantiate a new object with the members defined by that function.

    function Animal(species, animalName, food) {
      this.species = species;
      this.animalName = animalName;
      this.food = food;
      this.eat = function() {
        return this.animalName + " eats" + this.food;
      }
    }
    
    // We can create new instances of the animal
    const lion = new Animal('Felidae', 'lion', 'meet');
    // Es2015  using class 
    class Animal {
      constructor(species, animalName, food) {
        this._species = species;
        this._animalName = animalName;
        this._food = food;
    
        this.eat = function() {
          return this.animalName + " eats" + this.food;
        }
      }
    }
    var rabbit = new Animal('Leporidae', 'rabbit', 'grass');
  • Singleton Pattern:

    In Singleton pattern only one instance of a class can exist. It's implemented in such a way that if no instance of the singleton class exists then a new instance is created and returned, but if an instance already exists, then the reference to the existing instance is returned.

    Singletons serve as a shared resource namespace. It also serves good in the condition where a single instance is only needed like database connection.

    class Logger {
      constructor(name = "") {
        if (!!Logger.instance) {
          return Logger.instance;
        }
        Logger.instance = this;
        this.name = name;
        this.createLoggerId = "id_" + Math.random();
        return this;
      }
    
      log(val) {
        console.log(val);
      }
    }

    Of course, Singleton comes with its own set of the problem -

    1. The instance once created can't be changed. for eg:

      const instanceOne = new Logger ("One");
      const instanceTwo = new Logger ("Two");
      const instanceThree = new Logger ();
      console.log(`Name of instanceOne is "${instanceOne.getName()}"`);
      console.log(`Name of instanceTwo is "${instanceTwo.getName()}"`);
      console.log(`Name of instanceThree is "${instanceThree.getName()}"`);

      All this will print One only because It always uses the one instance that was created in the beginning.

    2. Singletons, technically, can't be extended, they don't have any ancestors. The only way to extend a singleton class is to do it before any instance is initiated. But this is an extreme anti-pattern because you can't be sure that someone will not use the base class before you do the extending.

Too many singletons in the codebase often indicate that modules in a system are either tightly coupled or that logic is overly spread across multiple parts of a codebase. Singletons can be more difficult to test due to issues ranging from hidden dependencies, the difficulty in creating multiple instances, difficulty in stubbing dependencies, and so on.

  • Factory Pattern

    Factory pattern is used to create a different type of object depending on conditions, factory pattern can provide a generic interface for creating objects, where we can specify the type of factory object we wish to be created. This pattern is frequently used when we need to manage or manipulate collections of objects that are different yet have many similar characteristics.It can be used for 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.

    class VehicleFactory {
    
      constructor() {
    
        this.createVehicle = function(configs) {
    
          let vehicle;
          const {
            type
          } = configs;
    
          switch (type) {
    
            case "Car":
              vehicle = new Car(configs);
              break;
    
            case "Truck":
              vehicle = new Truck(configs);
              break;
    
            default:
              return null;
          }
    
          vehicle.genericInformation = () => {
            console.log("heavy vehicle");
          }
    
          return vehicle;
        }
    
      }
    }
    
    class Car {
    
      constructor(options) {
        // some defaults
        this.type = "Car";
        this.doors = options.doors || 4;
        this.state = options.state || "brand new";
        this.color = options.color || "silver";
      }
    
      carForMe = (option) => {
    
        const {
          coPassenger,
          safetyLevel,
          sunRoof
        } = options;
    
        if (coPassenger >= 2 && safetyLevel <= 5 && sunRoof) return "You need a fast and furious car";
        else return "You are in for a cutesy small car. :) "
      }
    }
    class Truck {
      constructor(options) {
        this.type = "Truck";
        this.state = options.state || "used";
        this.wheelSize = options.wheelSize || "large";
        this.color = options.color || "blue";
      }
    }

    This could seem similar to builder pattern but it’s different as here object is created in one method together with attributes but in builder pattern we create an object and its attribute in steps so we have control which attribute we want to set.

  • Prototype Pattern:

    The prototype pattern is one which creates objects based on a template of an existing object through cloning. We can think of the prototype pattern as being based on prototypal inheritance where we create objects which act as prototypes for other objects. The prototype object itself is effectively used as a blueprint for each object the constructor creates. One of the benefits of using the prototype pattern is that we're working with the prototypal strengths JavaScript has to offer natively rather than attempting to imitate features of other languages.

    With other design patterns, this isn't always the case. Not only is the pattern an easy way to implement inheritance, but it can also come with a performance boost as well: when defining a function in an object, they're all created by reference (so all child objects point to the same function) instead of creating their own individual copies.

    const car = {
      name: "Maruti",
      drive: function() {
        console.log("Weeee. I'm driving!");
      },
      panic: function() {
        console.log("Wait. How do you stop this thing?");
      }
    };
    
    const options = {
      "id": {
        value: '11',
        enumerable: true
      },
    
      "model": {
        value: "Ford",
        enumerable: true
      }
    }
    const yourCar = Object.create(car, options);

    This was it for this article. Next we will move on to different design patterns.

    All the code discussed here is available on GitHub link (https://github.com/overflowjs-com/design-pattern)

    Get yourself added to our 2500+ people subscriber family to learn and grow more and please hit the share button on this article to share with your co-workers, friends, and others.

    Check out articles on Javascript, Angular, Node.js, Vue.js

    For more articles stay tuned to overflowjs.com

    Happy coding❤️

Email

About Kirti Chaturvedi

Full-Stack web developer with 4+ experience.