Classic Pattern
It's typically around OOP solutions
They are categorized in: Creational, Structural, Behavioral
In JavaScript (<= ES5) many design patterns are now covered by ES6.
In JavaScript, there are many ways to implement the same design pattern, thanks to the dynamic nature of the language.
1) Creational Design Patterns
- These patterns aim to solve the problems associated with creating objects in a way that enhances flexibility and reuse of existing code. The primary purpose of creation patterns is to separate the logic of object creation from the rest of the code.
Singleton
Problem to Solve: Ensure that a class has ONLY ONE INSTANCE and provides a global point of access to it.
Solution: Restrict the instantiation of the class to ONE object and provide a method to access this instance.
Use cases
Managing a global configuration object.
Database connection pooling.
UI State Management
Logging service.
const DB = {
connect: async () => {},
sendQuery: async (query) => {},
};
Factory π
Problem to Solve: Object creation can become complex or may involve multiple steps, conditional logic, or dependencies.
Solution: The factory pattern encapsulates the object creation process within a separate method or class, isolating it from the rest of the application logic.
Use cases
UI Element creation
Different types of notifications
Data parsers
2) Structural Design Patterns πΌ
They are solutions for COMPOSING CLASSES and OBJECTS to form larger structures while keeping them flexible and efficient.
They focus on simplifying relationships between entities to ensure the system's maintainability and scalability.
Decorator Pattern π
Problem to Solve: Add additional functionality to objects dynamically without modifying their structure.
Solution: Wrap the object with another object that adds the desired behavior.
Use cases
Extending User Interface components with additional features.
Adding validation, and caching to method calls.
Wrapping API responses to format or process data before passing it on.
Adapter Pattern
Problem to Solve: Allow INCOMPATIBLE interfaces to work together.
Solution: Create an adapter that translates one interface into another that a client expects.
Use cases:
Adapting legacy code to work with new systems or APIs
Integrating third-party libraries with different interfaces into your application
Converting data formats.
Mixins Pattern
Problem to Solve: SHARE functionality between classes WITHOUT using inheritance. Because when we change an object's inheritance, we change its nature.
Solution: Create a class containing methods that can be used by other classes and apply it to multiple classes.
For example:
let greetingMixin = {
sayHi () {console.log(`Hi there, my name is ${this.name}`);}
};
class User {
constructor(name) {
this.name = name;
}
};
Object.assign(User.prototype, greetingMixin);
I want to share the sayHi
functionality inside the greetingMixin
without using inheritance.
We can achieve that behavior with Object.assign
, we're injecting every behavior that we have in the greetingMixin
object into every object of the User
class.
Now we can go ahead, create an instance of User
and invoke sayHi
directly on the instance.
Additional, by applying the Mixin pattern, we can have the power to injecting behavior into the Class we don't own.
Value Object Pattern
Problem to Solve: Represent a value that is immutable, distinct from other objects based on its properties rather than its identity.
Solution: Create a class where instances are considered EQUAL if all their properties are equal, and ensure the object is immutable.
Use Cases:
- Representing complex data types like money, dates, or coordinates.
Example:
class Money {
constructor(amount, currency) {
this.amount = amount;
this.currency = currency;
// Freeze the object to make it immutable
Object.freeze(this);
};
equals(other) {
return other instanceof Money &&
this.amount === other.amount &&
this.currentcy === other.currency;
};
}
3) Behavioral Design Patterns
- Deal with object INTERACTION and Responsibility Distribution. These patterns focus on HOW objects COMMUNICATE and COOPERATE, ensuring that the system is Flexible and easy to extend.
Observer Pattern (Publisher - Subscriber)
Problem to Solve: Allow an object (Subject) to NOTIFY other object (Observers) about CHANGES in ITS STATE without requiring them to be tightly COUPLED.
Solution: Define a subject that maintains a list of observers and notifies them of any STATE CHANGES, typically by calling one of their methods.
Use Cases:
Event handlers.
Real-time notifications
UI updates
Code example:
class Subject {
constructor() {
this.observers = new Set();
}
addObserver(obs) {this.observers.add(obs)}
removeObserver(obs) {this.observers.delete(obs)};
notifyObservers(message) {
this.observers.forEach(ob => ob(message));
}
};
// Usage
subject1.addObserver(message => console.log(`Event fired!`));
Memento Pattern
Problem to Solve: Capture and externalize an object's internal state so that it can be restored later, without violating encapsulation.
Solution: Create an object that stores the state of the original object and provide methods to save and restore the state
Use Cases:
Undo / Redo functionality
Saving an app session
Time-travel debugging
Code example:
class Subject {
constructor() {
this.history = [];
}
push(state) {this.history.push(createMemento())};
pop() {
if (this.history.length === 0) return null;
return this.history.pop();
};
};
Reference Source: https://firt.dev/