Observer

Observer Pattern

Introduction

The Observer pattern is a behavioral design pattern that defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically. This pattern is widely used in implementing distributed event handling systems, in “listener” scenarios.

Purpose

  • To establish a one-to-many relationship between objects so that when one object changes state, all its dependents are notified and updated automatically.
  • To support a broadcast-type communication between objects.

Use Cases

  • When a change to one object requires changing others, and the number of objects that need to be changed is unknown.
  • When an object should be able to notify other objects without making assumptions about who these objects are.

Advantages/Disadvantages

Advantages:

  • Promotes the principle of loose coupling.
  • Allows sending data to other objects effectively without any change in the Subject or Observer classes.

Disadvantages:

  • Can lead to unexpected updates, which might be hard to track.
  • Might result in memory leaks called “lapsed listener problem” if observers are not properly unregistered.

Implementation in JavaScript

javascript
	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 {
	  update(data) {
	    console.log(`Observer received data: ${data}`);
	  }
	}
	
	// Usage
	const subject = new Subject();
	const observer1 = new Observer();
	const observer2 = new Observer();
	
	subject.subscribe(observer1);
	subject.subscribe(observer2);
	
	subject.notify('Hello World'); // Both observers receive the update

Practical Example

Imagine a backend scenario where you have a system monitoring service that notifies multiple services (like logging, analytics, and user notification systems) whenever a critical event occurs.

javascript
	class SystemMonitor {
	  constructor() {
	    this.observers = [];
	  }
	
	  subscribe(observer) {
	    this.observers.push(observer);
	  }
	
	  notify(event) {
	    this.observers.forEach((observer) => observer.notify(event));
	  }
	}
	
	class LoggingService {
	  notify(event) {
	    console.log(`Logging: ${event}`);
	  }
	}
	
	class AnalyticsService {
	  notify(event) {
	    console.log(`Analyzing: ${event}`);
	  }
	}
	
	class UserNotificationService {
	  notify(event) {
	    console.log(`Notifying users: ${event}`);
	  }
	}
	
	// Usage
	const monitor = new SystemMonitor();
	const logger = new LoggingService();
	const analytics = new AnalyticsService();
	const userNotifier = new UserNotificationService();
	
	monitor.subscribe(logger);
	monitor.subscribe(analytics);
	monitor.subscribe(userNotifier);
	
	monitor.notify('Server CPU usage at 90%');

In this example, SystemMonitor acts as the subject, and services like LoggingService, AnalyticsService, and UserNotificationService act as observers. Whenever the system monitor detects a critical event, all subscribed observers are notified and can respond accordingly.