Observer Design Pattern: The Power of Event Emitter

Design patterns are essential tools for building robust, maintainable, and scalable applications. Understanding and effectively utilizing design patterns can set you apart in frontend interviews. In this article, we will delve into the Observer Design Pattern, a powerful pattern that enables efficient communication between objects. This pattern is also known as the Publisher – Subscriber(Pub-Sub) pattern.

Observer Design Pattern

The Observer Pattern is a behavioural design pattern that defines a one-to-many dependency between objects. When one object (the subject) changes state, all its dependents (observers) are notified and updated automatically. This pattern is particularly useful for implementing distributed event-handling systems.

Implementing an Event Emitter in JavaScript is one of the most asked interview questions which uses the Observer Design Pattern.

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

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

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

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

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

  update(data) {
    console.log(`${this.name} received data: ${data}`);
  }
}

const subject = new Subject();

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

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

subject.notify('Hello, Observers!'); // Output: Observer 1 received data: Hello, Observers!
                                     //         Observer 2 received data: Hello, Observers!

subject.unsubscribe(observer1);

subject.notify('Observer 1 has unsubscribed'); // Output: Observer 2 received data: Observer 1 has unsubscribed

Why is the Observer Pattern Important?

  1. Decoupling: The Observer Pattern decouples the subject from its observers, allowing changes in the subject to be automatically reflected in the observers without the subject needing to know the details of the observers.
  2. Scalability: This pattern allows adding new observers without modifying the subject, making it easy to scale and extend the system.
  3. Flexibility: Observers can be added or removed at runtime, providing flexibility in handling dynamic and changing requirements.
  4. Efficiency: By notifying only the relevant observers, the Observer Pattern ensures efficient communication and updates, reducing unnecessary processing.

Real-Time Example: Weather Station

class WeatherStation {
  constructor() {
    this.observers = [];
    this.temperature = null;
  }

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

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

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

  setTemperature(temp) {
    console.log(`Setting temperature to ${temp}`);
    this.temperature = temp;
    this.notify();
  }
}

class TemperatureDisplay {
  update(temp) {
    console.log(`Temperature Display: ${temp} degrees`);
  }
}

class Fan {
  update(temp) {
    if (temp > 25) {
      console.log('Fan: It\'s hot, turning on...');
    } else {
      console.log('Fan: It\'s cool, turning off...');
    }
  }
}

const weatherStation = new WeatherStation();

const tempDisplay = new TemperatureDisplay();
const fan = new Fan();

weatherStation.subscribe(tempDisplay);
weatherStation.subscribe(fan);

weatherStation.setTemperature(20); 
// Output: Setting temperature to 20
//         Temperature Display: 20 degrees
//         Fan: It's cool, turning off...

weatherStation.setTemperature(30); 
// Output: Setting temperature to 30
//         Temperature Display: 30 degrees
//         Fan: It's hot, turning on...

Explanation of the Example

WeatherStation Class:

  • WeatherStation is the subject that maintains a list of observers and the current temperature.
  • The subscribe method adds an observer to the list.
  • The unsubscribe method removes an observer from the list.
  • The notify method calls the update method on each observer with the current temperature.
  • The setTemperature method sets the temperature and notifies all observers.

Observer Classes:

  • TemperatureDisplay and Fan are observer classes that implement the update method.
  • The TemperatureDisplay class simply logs the temperature.
  • The Fan class logs whether it is turning on or off based on the temperature.

Using the Observer Pattern:

  • Instances of TemperatureDisplay and Fan are created and subscribed to the WeatherStation.
  • When the temperature is set using setTemperature, all subscribed observers are notified and their update methods are called with the new temperature.

Importance in Real-World Applications

The Observer Pattern is widely used in real-world applications for implementing event-driven systems, such as GUIs, messaging systems, and real-time data feeds. It ensures that when an event occurs, all interested parties are notified and can react accordingly. By using the Observer Pattern, you can build more modular, flexible, and scalable applications.