DEV Community

Cover image for Using Design Patterns in JavaScript —The Ultimate Guide
Suresh Mohan for Syncfusion, Inc.

Posted on • Originally published at syncfusion.com on

Using Design Patterns in JavaScript —The Ultimate Guide

Design patterns are reusable, advanced, object-oriented solutions that we can use to address similar instances of everyday problems in software design. They are like blueprints for solving certain kinds of problems.

In JavaScript, design patterns can be beneficial when creating applications. That is because design patterns are straightforward solutions approved and confirmed by the developer community.

This article will discuss the benefits of using design patterns and introduce some of the most-used design patterns in JavaScript.

Benefits of Using Design Patterns

No need to start from scratch

We can quickly implement proven design patterns in our application without struggling. They are optimized and revised by experienced developers.

Reusable

We can use the same design pattern multiple times to address similar issues. They are not solutions for just one problem; we can implement them to address many issues.

Reduce the code size

Design patterns are already optimized, lowering the project’s overall complexity and improving performance when appropriately applied.

No need to refactor the code

There is no need to specifically refactor the codebase when using the design patterns properly. We can improve the maintainability and efficiency of the code.

Expressive and understandable

The code is clear and understandable to other developers if we follow a standard design pattern. That makes it easier to explain and discuss with other developers, with everyone on the same page.

Types of Design Patterns

There are various design patterns we can use in JavaScript applications. Let’s go through the three significant groups of design patterns.

Creational design patterns

Addresses problems by handling the object creation process. Examples: prototype pattern, singleton pattern, constructor pattern.

Structural design patterns

Solves problems by identifying methods to realize relationships between objects. Examples: adapter pattern, proxy pattern.

Behavioral design pattern

Solves problems by handling communication between objects. Examples: observer pattern, template pattern.

Types of design patterns in JavaScript

Types of design patterns in JavaScript

Design Patterns in JavaScript

Now, let’s discuss some popular design patterns used in JavaScript.

Constructor pattern

The constructor pattern belongs to the creational design pattern category. It allows us to initialize new objects with specific methods and properties. We can use the constructor pattern when creating many instances of the same template. These instances may differ, but they can share their methods.

The following code shows a simple example of the constructor design pattern.

// Creating constructor
function Tree(name, height, scientificname) {
  this.name = name;
  this.height = height;
  this.scientificname = scientificname;
  // Declaring a method
  this.getData = function() {
    return this.name + ' has a height of ' + this.height;
  };
}
// Creating new instance
const Coconut = new Tree('coconut', '30m', 'cocos nucifera');
console.log(Coconut.getData()); // coconut has a height of 30m
Enter fullscreen mode Exit fullscreen mode

Here, the constructor function is Tree. It contains three attributes and one method named name, height, scientific name, and getData, respectively. After that, we instantiate a new object, Coconut, from it. This object contains all the properties and methods from the constructor. Then, we can pass the relevant data through the object and call the function to get the output of coconut has a height of 30m.

Factory pattern

The factory pattern is another creational design pattern, but the constructor accepts various arguments to return the desired object. This pattern is ideal for situations where we need to deal with different object groups with similar characteristics.

For example, consider a food manufacturing factory. If you ask for a cake, they give you a cake. If you want bread, they give you bread. That is the basic idea of what is going on in this pattern. So, let’s see how we can implement this in a codebase.

function foodFactory() {
  this.createFood = function(foodType) {
    let food;
switch(foodType) {
      case 'cake':
        food = new Cake();
        break;
      default:
        food = new Bread();
        break;
    }
return food;
  }
}
const Cake = function() {
  this.givesCustomer = () => {
    console.log('This is a cake!');
  }
}
const factory = new foodFactory();
const cake = factory.createFood('cake');
cake.givesCustomer();
Enter fullscreen mode Exit fullscreen mode

We have a factory called the foodFactory. There, we have the function createFood() that takes a parameter, foodType. We can instantiate objects depending on this parameter. For example, if we pass the parameter as cake to the createFood() function, we receive the corresponding output. So, this design pattern helps to instantiate different objects with similar characteristics.

Module pattern

The module pattern belongs to the structural design patterns category. This pattern is similar to encapsulation. It lets you change and update a particular piece of code without affecting other components. It is self-contained, and this module helps keep our code clean and separated.

As JavaScript does not have access specifiers like public or private, we can use this module pattern to implement encapsulation where necessary. We use closures (a function that can access the parent scope even after the parent function is closed) and an immediately invoked function expression (IIFE) to implement it.

We can use this design pattern to emulate encapsulation and hide certain components from the global scope.

Let’s see an example of how actual code is built based on this design pattern.

function foodContainter () {
    const container = \[\];

    function addFood (name) {
      container.push(name);
    }

    function getAllFood() {
      return container;
    }

    function removeFood(name) {
      const index = container.indexOf(name);
      if(index < 1) {
        throw new Error('This food is unavailable');
      }
      container.splice(index, 1)
    }

    return {
      add: addFood,
      get: getAllFood,
      remove: removeFood
    }
}

const container = FoodContainter();
container.add('Cake');
container.add('Bread');

console.log(container.get()) // Gives both cake and bread
container.remove('Bread') // Removes bread
Enter fullscreen mode Exit fullscreen mode

At the beginning of the code, there is an array container to include all the elements we enter. The method addFood() performs a push operation each time it’s called to enter a new element into the array. Then, the getAllFood() function returns all the elements available inside the array. The removeFood() method checks whether the entered item is available inside the array and removes it if it is there. Otherwise, it throws an error: This food is unavailable.

After this implementation, we can create new objects and use the three methods previously listed. Any changes that we make within the foodContainer do not affect any other component.

Singleton pattern

The singleton pattern restricts an object from instantiating more than once. It instantiates one instance from a class if one does not already exist.

We can implement this by creating a class with a method that creates an instance only if there are no other existing instances from the same class. If there is a current object, it will reference an already created object instead of a new one.

The singleton pattern is helpful in reducing namespace pollution, and it also minimizes the need for global variables. We can use this design pattern to coordinate system-wide operations from a single place.

Example: A database connection pool to manage access to a resource shared by the whole application.

var firstSingleton = (function () {
    var instance;
function createInstance() {
        var object = new Object("First instance");
        return object;
    }
return {
        getInstance: function () {
            if (!instance) {
                instance = createInstance();
            }
            return instance;
        }
    };
})();
function execute() {
var firstinstance = firstSingleton.getInstance();
var secondinstance = firstSingleton.getInstance();
console.log("Is it the same instance? " + (instance1 === instance2));
}
Enter fullscreen mode Exit fullscreen mode

The createInstance() method creates a new object. The getInstance() function acts as the singleton’s gatekeeper and checks for the availability of an object to create a new one or reference an already existing one.

A part of the getInstance() method represents another design pattern, the lazy load. This new design pattern checks for the availability of an existing instance and creates one if an instance does not exist. This helps to save memory and CPU by creating instances only when required.

In the execute() method, we create two instances and compare them to check whether the two instances are the same. It will give the output “Is it the same instance? true” since any other instance creation will just return a reference to the first instance itself.

Prototype pattern

The prototype pattern clones the attributes of an existing object into new objects. The basic concept of this design pattern is prototypal inheritance. It is about creating an object that acts as a prototype when instantiating new objects. We are using the Object.create feature of JavaScript to implement this.

JavaScript does not support the class concept. So, we can use this prototype pattern to implement inheritance in JavaScript applications.

Let’s take a bus as an example.

const bus = {
  wheels: 4,
  start() {
    return 'started';
  },
  stop() {
    return 'stopped';
  },
};
// create object
const myBus = Object.create(bus, { model: { value: 'Single deck' } });
console.log(myBus.\_\_proto\_\_ === bus); // true
Enter fullscreen mode Exit fullscreen mode

We create a prototype named bus, with one property and two methods named wheels, start(), and stopped(). Then, we make a new object from it, myBus, and give that new object an additional property, model. But this new object contains all the properties and methods from the prototype.

With the last line of code, we can prove that the prototype of the newly created object, myBus, is the same as the original prototype, bus.

Conclusion

This article discussed what a design pattern is, what benefits they carry, and what design patterns we can implement in JavaScript.

Although these design patterns help developers in many ways, we must only use them after going through the specs of an applications and making sure the design pattern suits those.

The Syncfusion Essential Studio for JavaScript will be the only suite you will ever need to build an application. It contains over 65 high-performance, lightweight, modular, and responsive UI components in a single package. Download the free trial to evaluate the controls today.

If you have any questions or comments, you can contact us through our support forums, support portal, or feedback portal. We are always happy to assist you!

I hope you have found this article useful. Thank you for reading.

Related blogs

Top comments (1)

Collapse
 
maxiim3 profile image
maxiim3

"JavaScript does not support the class concept.".... ES6?