Hone logo
Hone
Problems

Implement the Observer Pattern in JavaScript

The Observer pattern is a behavioral 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 crucial for building decoupled and event-driven systems, enabling efficient communication between different parts of an application without tight coupling.

Problem Description

Your task is to implement a basic Observer pattern in JavaScript. You need to create two main components:

  1. Subject Class: This class will manage a list of observers and notify them when its state changes.
  2. Observer Class: This class (or rather, an interface/structure) will define how an observer reacts to notifications from the subject.

Key Requirements:

  • Subject Class:
    • Must have a method subscribe(observer) to add an observer to its list.
    • Must have a method unsubscribe(observer) to remove an observer from its list.
    • Must have a method notify(data) to iterate through all subscribed observers and call their update method, passing data to them.
    • Should internally maintain a list of subscribed observers.
  • Observer Structure:
    • Each observer must have an update(data) method. This method will be called by the subject during notification.
    • Observers can be any object that implements the update method.

Expected Behavior:

When a Subject instance's notify method is called, each Observer instance that has been subscribed to that subject should have its update method invoked with the data passed to notify. Unsubscribing an observer should prevent it from receiving future notifications.

Edge Cases to Consider:

  • Subscribing the same observer multiple times.
  • Unsubscribing an observer that is not subscribed.
  • Notifying when there are no observers subscribed.

Examples

Example 1: Basic Notification

class ConcreteSubject extends Subject {
  constructor() {
    super();
    this.state = 0;
  }

  setState(newState) {
    this.state = newState;
    this.notify(this.state);
  }
}

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

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

const subject = new ConcreteSubject();
const observer1 = new ConcreteObserver("Observer 1");
const observer2 = new ConcreteObserver("Observer 2");

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

subject.setState(10);
// Expected Output:
// Observer 1 received update: 10
// Observer 2 received update: 10

subject.setState(20);
// Expected Output:
// Observer 1 received update: 20
// Observer 2 received update: 20

Example 2: Unsubscribing an Observer

// Using the same ConcreteSubject and ConcreteObserver classes from Example 1

const subject = new ConcreteSubject();
const observer1 = new ConcreteObserver("Observer 1");
const observer2 = new ConcreteObserver("Observer 2");

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

subject.setState(30);
// Expected Output:
// Observer 1 received update: 30
// Observer 2 received update: 30

subject.unsubscribe(observer1);

subject.setState(40);
// Expected Output:
// Observer 2 received update: 40

Example 3: Edge Cases

// Using the same ConcreteSubject and ConcreteObserver classes from Example 1

const subject = new ConcreteSubject();
const observer1 = new ConcreteObserver("Observer 1");

// Attempt to subscribe an observer multiple times
subject.subscribe(observer1);
subject.subscribe(observer1); // Should ideally not cause issues or duplicate notifications

subject.setState(50);
// Expected Output:
// Observer 1 received update: 50

// Attempt to unsubscribe an observer that's not subscribed
subject.unsubscribe(new ConcreteObserver("Observer 3"));

// Attempt to unsubscribe an observer that was already unsubscribed
subject.unsubscribe(observer1);

subject.setState(60);
// Expected Output:
// Observer 1 received update: 60 (if it was still subscribed at the time of notification, otherwise no output)

Constraints

  • The Subject class should manage its observers internally, for example, using an array or a Set.
  • The update method of an Observer should accept a single argument representing the data passed by the subject.
  • The implementation should be in plain JavaScript, without using external libraries for implementing the pattern itself.
  • Ensure that duplicate subscriptions of the same observer object do not lead to multiple calls to its update method for a single notify call.

Notes

  • Consider how you will handle the list of observers to avoid issues when an observer unsubscribes itself during a notification loop (though for this challenge, a simple loop is acceptable).
  • The Subject class itself can be an abstract concept or a base class. You might want to create a concrete implementation of Subject for testing.
  • Think about the most efficient way to store and manage the observers for quick addition, removal, and iteration.
Loading editor...
javascript