JavaScript 101: Design Patterns

javascript-logo

This article is part of the JavaScript 101 series, you can find the previous part on the following link: JavaScript 101: ES6 and Beyond.

What is design pattern

In software development, a design pattern is a reusable solution for common problems in software design. Design patterns represent the best practices used by the software developers. We can think of design pattern like a programming template.

Why should we use design patterns

Every developer wants to write clean, maintainable, readable, and reusable code. Code structuring becomes more and more important as applications become larger. The concept of design patterns has been around in the software engineering industry since the very beginning, but they weren’t formalized. Design Patterns: Elements of Reusable Object-Oriented Software also called the Gang of Four, were trying to formalize the concept of design patterns in software engineering. Today the design patterns are a very important part of software development. Here we will talk about a couple of them, most important for the JavaScript programming language, but as they are very important for every software developer, feel free to check the books linked above.

Module design pattern

Modules design pattern is one of the most used design patterns in JavaScript, for keeping particular modules of code independent of other components. This provides loose coupling and well-structured code. Modules in JavaScript are like classes in object-oriented languages, and one of the many advantages is encapsulation for protecting the state and behavior from outside. With the module pattern, we can set public or private access level. Before ES6, programming language JavaScript didn't have build-in modules, so developers had to use the module pattern to implement modules. With ES6 JavaScript has native modules, that are stored in js files. Here you can check more about the ES6, and in the next example we will look at the ES6 module design pattern:

// math.js
function add(num1, num2) {
  console.log('Add:', num1, num2);
  return num1 + num2;
}
function sub(num1, num2) {
  console.log('Sub:', num1, num2);
  return num1 - num2;
}
export {add, sub};

// main.js
import { add, sub } from './math.js';
console.log(add(2, 2)); //4
console.log(sub(2, 2)); //0

Prototype design pattern

Prototype design pattern is an object-based creational design pattern and relies on the JavaScript prototypical inheritance. The prototype model is used for creating objects. The objects created are clones of the original object. In our example, we will use a car object that we use as a prototype to create another object, and we will define one more property on the new object.

//original object
const car = {
  manufacturer: 'Tesla',
  start() {
    return 'started';
  },
  stop() {
    return 'stopped';
  },
};

// we will be using Object.create to create the new object from the original one
// Object.create(proto[, propertiesObject])
const myCar = Object.create(car, { model: { value: 'Model S' } });
console.log(myCar.__proto__ === car); // true

We can see in the following diagram how to prototypal inheritance is working:

javascript-prototypal-inheritance UML Diagram for Prototypal Inheritance (Source: Wikipedia)

Observer design pattern

Observer design pattern is when one object (publisher) changes its state, all the other dependent objects (subscribers) are updated automatically. If you have used addEventListener or jQuery .on you will be familiar with the pattern. It has the most influence over Reactive Programming like RxJS. Let's check how the observer pattern is working in the next example:

const Subject = function() {
  this.observers = [];

  return {
    subscribeObserver: function(observer) {
      this.observers.push(observer);
    },
    unsubscribeObserver: function(observer) {
      let index = this.observers.indexOf(observer);
      if(index > -1) {
        this.observers.splice(index, 1);
      }
    },
    notifyObserver: function(observer) {
      let index = this.observers.indexOf(observer);
      if(index > -1) {
        this.observers[index].notify(index);
      }
    },
    notifyAllObservers: function() {
      for(let i = 0; i < this.observers.length; i++){
        this.observers[i].notify(i);
      };
    }
  };
};

const Observer = function() {
  return {
    notify: function(index) {
      console.log("Observer " + index + " is notified!");
    }
  }
}

const subject = new Subject();
const observer1 = new Observer();
const observer2 = new Observer();

subject.subscribeObserver(observer1);
subject.subscribeObserver(observer2);

subject.notifyObserver(observer2); // Observer 2 is notified!

subject.notifyAllObservers();
// Observer 1 is notified!
// Observer 2 is notified!

And now let's check the diagram for the observer pattern.

javascript-observer-pattern UML Diagram for Observer Pattern (Source: Wikipedia)

Singleton design pattern

A Singleton is an object which can only be instantiated once. Singleton creates a new instance of a class if one not exists. If there is an instance of that class, it will return the reference of that object instead. JavaScript has singleton by default, but it is not called singleton instead it is called object literal. For example:

const car = {
  manufacturer: 'Tesla',
  model: 'S',
  fuel: 'electricity',
  start: function() {
    console.log('started');
  }
}

Each object in JavaScript programming language occupies a unique memory location and when we call the car object, we are returning a reference to this object. If we try to copy the car variable into another variable and modify it, we would see both of the objects are modified because every object in JavaScript is passed by reference, not by value. So there is only a single object. Let's implement singleton with the constructor function first:

let instance = null;
function Car() {
  if(instance) {
    return instance;
  }
  instance = this;
  this.manufacturer = 'Tesla';
  this.model = 'S';
  
  return instance;
}
const carOne = new Car();
const carTwo = new Car();
console.log(carOne === carTwo); // true

Let's see how we can implement singleton with the module pattern:

const singleton = (function() {
  let instance;
  
  function init() {
    return {
      manufacturer = 'Tesla',
      model = 'S',
    };
  }
  return {
    getInstance: function() {
      if(!instance) {
        instance = init();
      }
      
      return instance;
    }
  }
})();
const instanceA = singleton.getInstance();
const instanceB = singleton.getInstance();
console.log(instanceA === instanceB); // true

Here we are creating a new instance by calling the singleton.getInstance, if an instance doesn't exist it will create a new instance with the init(), if an instance already exists, we would get that instance instead.

Conclusion

Design patterns are a very important part of software engineering and can be very helpful in solving common problems. But this is a very broad subject, and it is simply not possible to include everything about them. I suggest you take a look at these books:

  1. Design Patterns: Elements Of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides (Gang of Four)
  2. JavaScript Patterns by Stoyan Stefanov



#javascript #js #designpatterns

Author: Aleksandar Vasilevski |

Loading...