Understanding Modern Design Patterns in JavaScript

Introduction

Design patterns are reusable solutions to common problems encountered in software design. They aren't templates or blueprints—rather, they offer guidelines on how to structure code to solve certain problems. This article explores modern design patterns commonly used in JavaScript, offering practical examples and recommending resources for deeper understanding.

Singleton Pattern

Overview

The Singleton pattern restricts a class from instantiating multiple objects. It is used in scenarios where a single instance of a class controls access to a resource, like a database or a file.

Learn More About Singleton Pattern

Example

class Singleton {
  constructor() {
    if (!Singleton.instance) {
      Singleton.instance = this;
    }
    return Singleton.instance;
  }
}

const instance1 = new Singleton();
const instance2 = new Singleton();

console.log(instance1 === instance2);  // Output: true

Factory Method Pattern

Overview

The Factory Method is a creational pattern providing an interface for creating objects but allowing subclasses to alter the type of objects that will be created.

Learn More About Factory Method Pattern

Example

class CarFactory {
  createCar(type) {
    if (type === "Sedan") {
      return new Sedan();
    } else if (type === "SUV") {
      return new SUV();
    }
  }
}

class Sedan {
  constructor() {
    this.type = "Sedan";
  }
}

class SUV {
  constructor() {
    this.type = "SUV";
  }
}

const factory = new CarFactory();
const mySedan = factory.createCar("Sedan");
console.log(mySedan.type);  // Output: Sedan

Observer Pattern

Overview

The Observer pattern offers a subscription model where objects subscribe to an event and get notified when the event occurs. This pattern is commonly used in distributed event handling systems.

Learn More About Observer Pattern

Example

class Observer {
  constructor() {
    this.observers = [];
  }

  subscribe(fn) {
    this.observers.push(fn);
  }

  unsubscribe(fn) {
    this.observers = this.observers.filter(subscriber => subscriber !== fn);
  }

  notify(data) {
    this.observers.forEach(observer => observer(data));
  }
}

const obs = new Observer();
obs.subscribe(data => console.log(`Observer 1: ${data}`));
obs.subscribe(data => console.log(`Observer 2: ${data}`));
obs.notify("Event Fired");  // Output: Observer 1: Event Fired, Observer 2: Event Fired

Module Pattern

Overview

The Module pattern is used to create a simple way to encapsulate methods and attributes, providing a 'privacy' and state retention mechanism. It is particularly useful for singletons or objects with a single instance.

Learn More About Module Pattern

Example

const Calculator = (() => {
  // private methods
  const add = (a, b) => a + b;
  const sub = (a, b) => a - b;
  
  return {
    // public methods
    operate: (op, a, b) => {
      if (op === 'add') {
        return add(a, b);
      }
      if (op === 'sub') {
        return sub(a, b);
      }
    }
  };
})();

console.log(Calculator.operate('add', 5, 3));  // Output: 8

Strategy Pattern

Overview

The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

Learn More About Strategy Pattern

Example

class SortStrategy {
  sort(arr) {}
}

class BubbleSort extends SortStrategy {
  sort(arr) {
    // Bubble sort algorithm
    return arr.sort((a, b) => a - b);
  }
}

class QuickSort extends SortStrategy {
  sort(arr) {
    // Quick sort algorithm
    return arr.sort((a, b) => a - b);  // Placeholder
  }
}

class Sorter {
  constructor(strategy) {
    this.strategy = strategy;
  }

  sort(arr) {
    return this.strategy.sort(arr);
  }
}

const bubbleSortStrategy = new BubbleSort();
const sorter = new Sorter(bubbleSortStrategy);
console.log(sorter.sort([5, 3, 9, 1]));  // Output: [1, 3, 5, 9]

Conclusion

Design patterns offer flexible and clean ways to compose complex solutions in JavaScript. Understanding when and how to use them can significantly improve code readability and maintainability. While this article provides a starting point, the world of design patterns is rich and varied. Continuous learning and practice are essential for mastering them.

Essential JavaScript Design Patterns For Beginners by Addy Osmani is a highly recommended read for anyone wishing to delve deeper into this subject.