JavaScript,OOP

Created

August 16, 2023

Common design patterns in JavaScript

Design patterns are general reusable solutions to common problems encountered in software design. Below are some common design patterns in JavaScript along with examples:

1. Singleton Pattern

Ensures that a class has only one instance and provides a global point of access to that instance.

const Singleton = (function () {
  let instance;

  function createInstance() {
    return { key: 'value' };
  }

  return {
    getInstance: function () {
      if (!instance) {
        instance = createInstance();
      }
      return instance;
    }
  };
})();

const instance1 = Singleton.getInstance();
const instance2 = Singleton.getInstance();
console.log(instance1 === instance2); // true

2. Factory Pattern

Defines an interface for creating an object but lets subclasses alter the type of objects that will be created.

function CarFactory() {}

CarFactory.prototype.createCar = function (type) {
  let car;

  if (type === 'SUV') {
    car = new SUV();
  } else if (type === 'Sedan') {
    car = new Sedan();
  }

  return car;
};

function SUV() { /* ... */ }
function Sedan() { /* ... */ }

const factory = new CarFactory();
const suv = factory.createCar('SUV');

3. Module Pattern

Encapsulates private data by exposing only certain properties and methods.

const MyModule = (function () {
  let privateData = 42;

  function privateFunction() {
    return 'This is private!';
  }

  return {
    publicMethod: function () {
      return privateFunction() + ' But this is public!';
    }
  };
})();

console.log(MyModule.publicMethod()); // This is private! But this is public!

4. Observer Pattern

Defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

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

  addObserver(observer) {
    this.observers.push(observer);
  }

  removeObserver(observer) {
    const index = this.observers.indexOf(observer);
    if (index > -1) {
      this.observers.splice(index, 1);
    }
  }

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

class Observer {
  update(data) {
    console.log('Observer updated with data:', data);
  }
}

const subject = new Subject();
const observer = new Observer();
subject.addObserver(observer);
subject.notify('Some data'); // Observer updated with data: Some data

5. Strategy Pattern

Defines a family of algorithms, encapsulates each one, and makes them interchangeable.

class StrategyA {
  execute() {
    return 'Strategy A';
  }
}

class StrategyB {
  execute() {
    return 'Strategy B';
  }
}

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

  executeStrategy() {
    return this.strategy.execute();
  }
}

const strategyA = new StrategyA();
const contextA = new Context(strategyA);
console.log(contextA.executeStrategy()); // Strategy A

const strategyB = new StrategyB();
const contextB = new Context(strategyB);
console.log(contextB.executeStrategy()); // Strategy B

6. MVC Pattern

A pattern that separates an application into three interconnected components: Model, View, and Controller.

  • Model: Represents the data and business logic.
  • View: Represents the UI.
  • Controller: Acts as an interface between Model and View.

Example implementations of MVC in JavaScript can be quite extensive as they typically involve DOM manipulation and potentially backend communication. Frameworks like Angular, React with Redux, or Backbone.js follow this pattern.

These patterns serve as a solid base for clean and maintainable code and are used in many modern JavaScript libraries and frameworks. Understanding them can improve the overall design and structure of your applications.

This example demonstrates a basic counter application, which allows you to increase or decrease the count.

Model

The Model represents the data and the business logic.

class Model {
  constructor() {
    this.count = 0;
    this.observers = [];
  }

  increment() {
    this.count++;
    this.notifyObservers();
  }

  decrement() {
    this.count--;
    this.notifyObservers();
  }

  addObserver(observer) {
    this.observers.push(observer);
  }

  notifyObservers() {
    this.observers.forEach(observer => observer.update(this.count));
  }
}

View

The View represents the UI and listens for changes to the Model.

class View {
  constructor(model) {
    this.model = model;
    this.model.addObserver(this);
    this.display();
  }

  update(count) {
    this.display(count);
  }

  display(count = 0) {
    const counterElement = document.getElementById('counter');
    counterElement.textContent = `Counter: ${count}`;
  }
}

Controller

The Controller acts as an interface between the Model and the View.

class Controller {
  constructor(model, view) {
    this.model = model;
    this.view = view;

    this.initialize();
  }

  initialize() {
    document.getElementById('increment').addEventListener('click', () => {
      this.model.increment();
    });

    document.getElementById('decrement').addEventListener('click', () => {
      this.model.decrement();
    });
  }
}

HTML

This is the HTML code for the buttons and the display element.

<button id="increment">Increment</button>
<button id="decrement">Decrement</button>
<div id="counter">Counter: 0</div>

Initialization

You can connect everything together with the following code:

const model = new Model();
const view = new View(model);
const controller = new Controller(model, view);

Here, the Model is responsible for handling the data and the business logic, the View takes care of displaying the data, and the Controller handles user input and updates both the Model and the View.

Not sure which platform or technology to use?

We can turn different applications and technologies into a high-performance ecosystem that helps your business grow.