How do you implement design patterns in your code?
Arpit Nuwal

 

1️⃣ Singleton Pattern (Creational)

The Singleton pattern ensures that a class has only one instance and provides a global access point to that instance.

Use Case: When you need a single instance, like a database connection or configuration settings.

Implementation (JavaScript)

javascript
class Singleton { constructor() { if (Singleton.instance) { return Singleton.instance; } this.value = Math.random(); Singleton.instance = this; } getValue() { return this.value; } } const instance1 = new Singleton(); console.log(instance1.getValue()); // Random value const instance2 = new Singleton(); console.log(instance2.getValue()); // Same value as instance1

Explanation:

  • The first time the class is instantiated, it creates a new instance.
  • Subsequent instantiations return the same object (Singleton.instance).

2️⃣ Factory Pattern (Creational)

The Factory pattern is used to create objects without specifying the exact class of object that will be created. It abstracts the object creation logic into a separate method or class.

Use Case: When you have multiple classes with the same interface but different implementations.

Implementation (JavaScript)

javascript
class Car { drive() { console.log("Driving a car"); } } class Bike { drive() { console.log("Riding a bike"); } } class VehicleFactory { static createVehicle(type) { switch (type) { case 'car': return new Car(); case 'bike': return new Bike(); default: throw new Error("Unknown vehicle type"); } } } const car = VehicleFactory.createVehicle('car'); car.drive(); // "Driving a car" const bike = VehicleFactory.createVehicle('bike'); bike.drive(); // "Riding a bike"

Explanation:

  • The VehicleFactory class is responsible for creating objects, which are then used in the main application.
  • This way, you can decouple the client code from the specifics of object creation.

3️⃣ Observer Pattern (Behavioral)

The Observer pattern allows objects (observers) to listen for changes in another object (subject) without being tightly coupled to it. When the subject changes, all registered observers are notified.

Use Case: For event-driven applications like UI updates or real-time data feeds.

Implementation (JavaScript)

javascript
class Subject { constructor() { this.observers = []; } addObserver(observer) { this.observers.push(observer); } removeObserver(observer) { this.observers = this.observers.filter(obs => obs !== observer); } notifyObservers() { this.observers.forEach(observer => observer.update()); } } class Observer { constructor(name) { this.name = name; } update() { console.log(`${this.name} received update!`); } } const subject = new Subject(); const observer1 = new Observer("Observer 1"); const observer2 = new Observer("Observer 2"); subject.addObserver(observer1); subject.addObserver(observer2); subject.notifyObservers(); // "Observer 1 received update!" // "Observer 2 received update!"

Explanation:

  • Subject maintains a list of observers and notifies them whenever a change occurs.
  • Observer is notified when the subject updates, allowing them to respond appropriately.

4️⃣ Strategy Pattern (Behavioral)

The Strategy pattern enables the selection of an algorithm at runtime. This pattern is useful when you have multiple algorithms or operations that can be swapped dynamically.

Use Case: When you have several algorithms for a task and want to change them without modifying the client code.

Implementation (JavaScript)

javascript
class Context { constructor(strategy) { this.strategy = strategy; } executeStrategy(a, b) { return this.strategy.execute(a, b); } } class AdditionStrategy { execute(a, b) { return a + b; } } class SubtractionStrategy { execute(a, b) { return a - b; } } const context = new Context(new AdditionStrategy()); console.log(context.executeStrategy(5, 3)); // 8 context.strategy = new SubtractionStrategy(); console.log(context.executeStrategy(5, 3)); // 2

Explanation:

  • The context class can use any strategy to execute the algorithm.
  • The strategy can be changed dynamically without modifying the context.

5️⃣ Decorator Pattern (Structural)

The Decorator pattern allows you to add new functionality to an object without changing its existing code.

Use Case: When you need to extend an object's behavior without subclassing or modifying the original class.

Implementation (JavaScript)

javascript
class Car { drive() { console.log("Driving a car"); } } class CarWithAC { constructor(car) { this.car = car; } drive() { this.car.drive(); console.log("With air conditioning"); } } const myCar = new Car(); myCar.drive(); // "Driving a car" const myCarWithAC = new CarWithAC(myCar); myCarWithAC.drive(); // "Driving a car" + "With air conditioning"

Explanation:

  • You can add new features to an existing object without changing its code by "decorating" it. In this example, we added air conditioning to the car object.

Tips for Implementing Design Patterns

  1. Understand the problem first: Know the issue you're solving before picking a pattern.
  2. Avoid overuse: Use design patterns only when necessary. Applying too many patterns can make code overly complex.
  3. Keep it simple: Patterns should make your code simpler, not more complex.
  4. Consistency: Implement patterns consistently throughout your codebase for maintainability.