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:
SubjectClass: This class will manage a list of observers and notify them when its state changes.ObserverClass: This class (or rather, an interface/structure) will define how an observer reacts to notifications from the subject.
Key Requirements:
SubjectClass:- 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 theirupdatemethod, passingdatato them. - Should internally maintain a list of subscribed observers.
- Must have a method
ObserverStructure:- 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
updatemethod.
- Each observer must have an
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
Subjectclass should manage its observers internally, for example, using an array or a Set. - The
updatemethod of anObservershould 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
updatemethod for a singlenotifycall.
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
Subjectclass itself can be an abstract concept or a base class. You might want to create a concrete implementation ofSubjectfor testing. - Think about the most efficient way to store and manage the observers for quick addition, removal, and iteration.