Helder Esteves

Type and hit Enter to search

JavaScript
Uncategorized
React
Software Architecture
General
Management
Next.JS
Node.JS
TypeScript
JavaScript
Uncategorized
React
Software Architecture
General
Management
Next.JS
Node.JS
Software Architecture

5 JS Design Patterns Every Developer Should Know

Helder Esteves
Helder Esteves
March 10, 2023 5 Mins Read
462 Views
0 Comments

Design patterns are reusable solutions to common programming problems. They help developers write code that is more modular, maintainable, and efficient. Do you know all of these 5?

Singleton Pattern

This pattern ensures that there is only one instance of a class and provides a global point of access to that instance.

For example, a database connection object can be created as a Singleton to ensure that only one connection is made to the database throughout the entire application.

const DatabaseConnection = (function() {
  let instance = null;

  function createInstance() {
    // code to create a database connection
    return new Object("Database connection");
  }

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

const connection1 = DatabaseConnection.getInstance();
const connection2 = DatabaseConnection.getInstance();

console.log(connection1 === connection2); // true

In this example, DatabaseConnection is a singleton object that ensures only one instance of the object is created.

Factory Pattern

This pattern is used to create objects of different classes without exposing the creation logic to the client. It provides a central place to create objects and separates the object creation from the object usage.

For example, let’s say you’re building a game and you have different types of characters, such as warriors, mages, and archers. Instead of creating each character individually and repeating the same code, you can use the Factory pattern to create the characters based on their type.

class Character {
  constructor(name, type) {
    this.name = name;
    this.type = type;
  }
}

class Warrior extends Character {
  constructor(name) {
    super(name, 'warrior');
  }
}

class Mage extends Character {
  constructor(name) {
    super(name, 'mage');
  }
}

class CharacterFactory {
  createCharacter(name, type) {
    switch(type) {
      case 'warrior':
        return new Warrior(name);
      case 'mage':
        return new Mage(name);
      default:
        throw new Error('Invalid character type');
    }
  }
}

// Usage
const factory = new CharacterFactory();
const warrior = factory.createCharacter('Aragorn', 'warrior');
const mage = factory.createCharacter('Gandalf', 'mage');

In this example, Character is a factory object that creates different characters based on the name and type passed to its createCharacter() method.

Observer Pattern

This pattern defines a one-to-many dependency between objects. When one object changes its state, all the dependent objects are notified and updated automatically.

For example, let’s say you have an online store and you want to notify users when a product they’re interested in becomes available. You can use the Observer pattern to implement this functionality.

class Product {
  constructor(name, price) {
    this.name = name;
    this.price = price;
    this.observers = [];
  }

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

  removeObserver(observer) {
    this.observers = this.observers.filter(o => o !== observer);
  }

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

  setPrice(price) {
    this.price = price;
    this.notifyObservers();
  }
}

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

  update(product) {
    console.log(`${this.name}, the price of ${product.name} has changed to ${product.price}`);
  }
}

// Usage
const product = new Product('iPhone', 1000);
const user1 = new User('John');
const user2 = new User('Mary');
product.addObserver(user1);
product.addObserver(user2);
product.setPrice(900);

In this example, we have a Product class that represents a product in our store. It has a list of observers (users) that are interested in it, and methods to add, remove, and notify them. We also have a User class that represents a user that wants to be notified when the price of a product changes.

Finally, we create a Product instance, add two observers to it, and change its price. This triggers the notifyObservers method, which in turn calls the update method on each observer, notifying them of the price change. In this way, the Observer pattern allows us to decouple the product and the users, making it easy to add or remove observers without affecting the product class.

Decorator Pattern

This pattern allows you to add new functionality to an existing object without modifying its structure. It dynamically adds or overrides behavior in an existing method of an object.

For example, let’s say you have a website that sells different types of pizzas. Each pizza has a base price, but customers can add different toppings to their liking. You can use the Decorator pattern to implement this functionality.

class Pizza {
  constructor() {
    this.description = 'Unknown Pizza';
    this.price = 0;
  }

  getDescription() {
    return this.description;
  }

  getPrice() {
    return this.price;
  }
}

class Cheese extends Pizza {
  constructor(pizza) {
    super();
    this.pizza = pizza;
    this.description = `${pizza.getDescription()}, Cheese`;
    this.price = pizza.getPrice() + 2;
  }

  getDescription() {
    return this.description;
  }

  getPrice() {
    return this.price;
  }
}

class Pepperoni extends Pizza {
  constructor(pizza) {
    super();
    this.pizza = pizza;
    this.description = `${pizza.getDescription()}, Pepperoni`;
    this.price = pizza.getPrice() + 3;
  }

  getDescription() {
    return this.description;
  }

  getPrice() {
    return this.price;
  }
}

// Usage
let pizza = new Pizza();
pizza = new Cheese(pizza);
pizza = new Pepperoni(pizza);
console.log(pizza.getDescription()); // Unknown Pizza, Cheese, Pepperoni
console.log(pizza.getPrice()); // 5

In this example, we have a Pizza class that represents a pizza in our store. It has a base price and a description. We also have two decorator classes, Cheese and Pepperoni, which add cheese and pepperoni toppings to the pizza, respectively. Each decorator has a reference to the original pizza object, and it overrides the getDescription and getPrice methods to add its own description and price.

Finally, we create a Pizza instance, add cheese and pepperoni toppings to it, and print its description and price. This allows us to create pizzas with different combinations of toppings without having to create a separate class for each one.

Module Pattern

This pattern allows you to encapsulate a group of related functions, variables, and objects into a single entity, making it easy to manage and organize your code. It provides a way to create private and public variables and methods.

For example, let’s say you have an application with a feature that serves as a simple counter. With it, you can increase, reset, or get the counter. The Module pattern lets you organize all of this related functionality very effectively.

const MyModule = (function() {
  // Private variables and functions
  let counter = 0;

  const incrementCounter = function() {
    counter++;
  };

  // Public API
  return {
    getCount: function() {
      return counter;
    },

    increment: function() {
      incrementCounter();
    },

    reset: function() {
      counter = 0;
    }
  };
})();

// Usage
console.log(MyModule.getCount()); // 0
MyModule.increment();
MyModule.increment();
console.log(MyModule.getCount()); // 2
MyModule.reset();
console.log(MyModule.getCount()); // 0

In this example, we have a self-executing (IIFE) function that returns an object with three methods: getCount, increment, and reset. These methods access a private variable called counter, which is not visible outside of the function. The function itself is called immediately, and its return value is assigned to the variable MyModule. This creates a module that has a private state and a public API, allowing us to use it to manage the counter without polluting the global namespace.

Finally, we use the module’s methods to increment and reset the counter, and to retrieve its value.


There are many more design patterns that I haven’t listed. Let me know in the comments if you want me to make a part 2 to this list. Cheers!

Tags:

Tips

Share Article

Other Articles

opened secret door inside library
Previous

5 JavaScript Features You Didn’t Know About

low angle of black metal tower
Next

Top 5 Cross-platform Frameworks to Use in 2023

Next
low angle of black metal tower
March 12, 2023

Top 5 Cross-platform Frameworks to Use in 2023

Previous
March 6, 2023

5 JavaScript Features You Didn’t Know About

opened secret door inside library

No Comment! Be the first one.

    Leave a Reply Cancel reply

    Your email address will not be published. Required fields are marked *

    Subscribe
    Stay Updated!
    Get notified when awesome content is posted. Learn more everyday.
    Most Read
    blue UTP cord
    Uncategorized
    Setting up a TURN server with Node: Production-ready
    May 19, 2023
    5 Min Read
    yellow line on gray asphalt road
    Next.JS
    5 Next.JS Tips You Never Heard About
    March 27, 2023
    4 Min Read
    This is the sign you've been looking for neon signage
    General
    Mastering the Art of Software Development: Best Practices Cheat Sheet
    May 11, 2023
    4 Min Read
    Most Engagement
    bird's-eye view photography of city buildings
    React
    Best React.js Project Design and Architectures for Better Maintainability

    Learn how to improve the maintainability of your React.js projects using hooks. Explore best...

    September 19, 2023
    3 Min Read
    Most Recent
    bird's-eye view photography of city buildings
    React
    Best React.js Project Design and Architectures for Better Maintainability
    September 19, 2023
    3 Min Read
    blue ballpoint pen on white notebook
    Node.JS
    Optimal Node.JS API Routing: Architectures, Frameworks, Code Examples & Folder Structures
    September 17, 2023
    4 Min Read
    grayscale photo of person holding glass
    JavaScript
    Top 10 JavaScript Coding Challenges You Must Absolutely Know
    May 17, 2023
    9 Min Read

    Related Posts

    bird's-eye view photography of city buildings
    React
    Best React.js Project Design and Architectures for Better Maintainability
    September 19, 2023
    3 Min Read
    Read More
    four handheld tools on board
    React
    Tools to Speed Up React Component Creation
    May 5, 2023
    3 Min Read
    Read More
    low angle of black metal tower
    General
    Top 5 Cross-platform Frameworks to Use in 2023
    March 12, 2023
    4 Min Read
    Read More
    yellow line on gray asphalt road
    Next.JS
    5 Next.JS Tips You Never Heard About
    March 27, 2023
    4 Min Read
    Read More

    Helder Esteves

    Read everything there is to know about the JavaScript development ecosystem, from programming to project management.

    Recent

    Best React.js Project Design and Architectures for Better Maintainability
    September 19, 2023
    Optimal Node.JS API Routing: Architectures, Frameworks, Code Examples & Folder Structures
    September 17, 2023

    Categories

    JavaScript9
    Uncategorized4
    React3
    Software Architecture2

    Contact

    Reach out to us through helder.writer@gmail.com

    © 2022, All Rights Reserved.

    Go to mobile version