JavaScript

Top 5 Design Patterns We Really Use in JavaScript

Design patterns are proven solutions to recurring software design problems. While there’s a plethora of patterns, some stand out as particularly useful in JavaScript and TypeScript development. Let’s explore five of the most prevalent and impactful ones.

The Module Pattern

The Module pattern is a cornerstone of JavaScript encapsulation. By creating functions that return objects, we establish private variables and methods, accessible only within the module. This promotes data privacy and prevents naming conflicts.

Example:

const Counter = (function() {
  let count = 0;

  return {
    increment: function() {
      count++;
    },
    decrement: function() {
      count--;
    },
    getCount: function() {
      return count;   

    }
  };
})();

In this example, count is private, accessible only through the returned object’s methods. This ensures data integrity and prevents accidental modifications.

The Observer Pattern

The Observer pattern establishes a one-to-many dependency between objects. A subject maintains a list of observers, and when its state changes, it notifies all observers. This pattern is widely used in UI frameworks and event systems.

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

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

  unsubscribe(observer) {
    this.observers = this.observers.filter(obs => obs !== observer);
  }

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

class Observer {
  constructor(name) {
    this.name = name;
  }

  update(data)   
 {
    console.log(`${this.name} updated with value: ${data}`);
  }
}

// Usage:
const subject = new Subject();
subject.state = 'initial state';

const observer1 = new Observer('Observer 1');
const observer2 = new Observer('Observer 2');

subject.subscribe(observer1);
subject.subscribe(observer2);

subject.state   
 = 'new state'; // Notify observers
subject.unsubscribe(observer1);
subject.state = 'another state'; // Observer 2 will be notified

This factory can create different shapes based on the provided type, abstracting away the creation logic.

The Singleton Pattern

The Singleton pattern ensures that a class has only one instance. It’s often used for global objects or resources that should be shared across the application.

Example:

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

  log(message) {
    console.log(message);   

  }
}

This implementation creates a single instance of the Logger class, accessible through Logger.getInstance().

The Decorator Pattern

The Decorator pattern allows adding behaviors to objects dynamically without modifying the original class. This promotes flexibility and extensibility.

Example:

function Coffee() {
  this.cost = 1.0;
  this.description = 'Coffee';
}

function Milk(coffee) {
  this.coffee = coffee;
  this.cost = coffee.cost + 0.5;
  this.description = coffee.description + ' with milk';
}

// ... other decorators for sugar, etc.

The Factory Pattern

The Factory Pattern provides a centralized interface for creating objects. Instead of instantiating objects directly, clients use the factory to request objects, allowing for flexibility and decoupling.  

Example:

class Shape {
  constructor(type) {
    this.type = type;
  }

  draw() {
    console.log(`Drawing a ${this.type}`);
  }
}

class ShapeFactory {
  getShape(type) {
    switch (type) {
      case 'circle':
        return new Shape('circle');
      case 'square':
        return new Shape('square');
      case 'triangle':
        return new Shape('triangle');
      default:
        return null;
    }
  }
}

// Usage:
const shapeFactory = new ShapeFactory();
const circle = shapeFactory.getShape('circle');
circle.draw(); // Output: Drawing a circle

In this example, the ShapeFactory encapsulates the creation logic for different shapes. Clients can request specific shapes without knowing their implementation details.

Conclusion

Design patterns are invaluable tools in a JavaScript developer’s arsenal. By understanding and effectively applying patterns like the Module, Observer, Factory, and Singleton patterns, you can significantly enhance code quality, maintainability, and scalability.

These patterns provide structured solutions to common programming challenges, promoting code reusability and promoting collaboration within development teams. While this exploration has covered some of the most prevalent patterns, the JavaScript ecosystem offers a rich tapestry of design patterns, each with its unique strengths and applications.

Eleftheria Drosopoulou

Eleftheria is an Experienced Business Analyst with a robust background in the computer software industry. Proficient in Computer Software Training, Digital Marketing, HTML Scripting, and Microsoft Office, they bring a wealth of technical skills to the table. Additionally, she has a love for writing articles on various tech subjects, showcasing a talent for translating complex concepts into accessible content.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button