Observer Design Pattern
Structural: Observer Pattern
What is the Observer Design Pattern?
View Answer:
The observer pattern is at the heart of event-driven programming. We create event handler routines that are informed when a specific event occurs.
The objects participating in this pattern are:
Subject -- example code: Click
- maintains a list of observers. Any number of Observer objects can observe a single Observer object.
- The subject implements an interface through which observer objects can subscribe and unsubscribe.
- When its state changes, it sends a notification to its observers.
Observer -- example code: clickHandler
- includes a function signature that gets called when the Subject changes (i.e., an event occurs)
In Modern JavaScript, you might implement the Observer pattern like this:
class Observable {
constructor() {
this.observers = [];
}
subscribe(f) {
this.observers.push(f);
}
unsubscribe(f) {
this.observers = this.observers.filter(subscriber => subscriber !== f);
}
notify(data) {
this.observers.forEach(observer => observer(data));
}
}
// Example usage:
const observable = new Observable();
// Observers
const observer1 = data => console.log(`Observer 1: ${data}`);
const observer2 = data => console.log(`Observer 2: ${data}`);
const observer3 = data => console.log(`Observer 3: ${data}`);
observable.subscribe(observer1);
observable.subscribe(observer2);
observable.subscribe(observer3);
observable.notify('notified!');
// Example output:
// Observer 1: notified!
// Observer 2: notified!
// Observer 3: notified!
In this example, the Observable
class represents the subject. It has three primary methods: subscribe
, unsubscribe
, and notify
. subscribe
adds a new observer to the list, unsubscribe
removes an observer, and notify
goes through each observer and calls it with the provided data.
Then we define three observer functions, observer1
, observer2
, and observer3
. Each of these is a function that logs a message to the console.
We create a new instance of Observable
, subscribe the observers, and then call notify
, which triggers each of the observers and logs the corresponding messages to the console.
The Observer pattern belongs to which pattern category?
View Answer:
Can you explain the Observer Pattern's use case?
View Answer:
Use Cases:
- To improve code management: We break down large programs into a system of loosely connected objects.
- To increase flexibility by allowing a dynamic relationship between observers and subscribers, which would otherwise be impossible due to tight coupling.
- To increase communication between the application's many components.
- To establish a one-to-many dependency between weakly related items.
Here's a practical example of using the Observer pattern in modern JavaScript to handle updates to a user's profile.
// Defining the Observable class
class Observable {
constructor() {
this.observers = [];
}
subscribe(f) {
this.observers.push(f);
}
unsubscribe(f) {
this.observers = this.observers.filter(subscriber => subscriber !== f);
}
notify(data) {
this.observers.forEach(observer => observer(data));
}
}
// User Profile Observable
const userProfileObservable = new Observable();
// Observer 1: Display User Profile
const displayProfile = profile => console.log(`Display Profile: ${profile.name}, ${profile.email}`);
// Observer 2: Update User Menu
const updateUserMenu = profile => console.log(`Update User Menu: ${profile.name}`);
// Observer 3: Send Profile Update Confirmation
const sendConfirmation = profile => console.log(`Send Confirmation Email to: ${profile.email}`);
// Subscribe Observers to the User Profile Observable
userProfileObservable.subscribe(displayProfile);
userProfileObservable.subscribe(updateUserMenu);
userProfileObservable.subscribe(sendConfirmation);
// Simulating a profile update
const updatedProfile = {
name: 'John Doe',
email: 'johndoe@example.com'
};
// Notify all observers about the updated profile
userProfileObservable.notify(updatedProfile);
// Example output:
// Display Profile: John Doe, johndoe@example.com
// Update User Menu: John Doe
// Send Confirmation Email to: johndoe@example.com
In this example, when a user's profile is updated, several different actions need to take place across the system:
- The displayed user profile must be updated (
displayProfile
). - The user menu must reflect the changes (
updateUserMenu
). - A confirmation email should be sent to the updated email (
sendConfirmation
).
By using the Observer pattern, each of these actions can be handled independently as observers that respond to the changes in the observable user profile. This makes the system more flexible and easier to extend (for example, by adding more observers) in the future.
What are some of the advantages of employing the Observer pattern?
View Answer:
- The Open/Closed Principle -- You can add new subscriber classes without modifying the publisher's code (and vice versa if a publisher interface exists).
- At runtime, you can create relationships between objects.
What's a disadvantage of the Observer Pattern?
View Answer:
- Sends notifications to subscribers in random order.
Are there any alternatives to using the Observer pattern?
View Answer:
What are the key components of the Observer Pattern?
View Answer:
The Observer Pattern consists of three key components:
Subject: This maintains a list of observers, facilitates adding or removing observers, and is responsible for notifying observers of changes.
Observer: This defines an updating interface for objects that should be notified of changes in the subject.
ConcreteSubject: This broadcasts notifications to observers on state changes and stores the state of the ConcreteSubject.
ConcreteObserver: This stores a reference to the ConcreteSubject, implements an update interface for the Observer, and maintains the observer's state.
Here's a code example demonstrating these components in JavaScript:
// Subject
class Subject {
constructor() {
this._observers = [];
}
subscribe(observer) {
this._observers.push(observer);
}
unsubscribe(observer) {
this._observers = this._observers.filter(obs => obs !== observer);
}
fire(change) {
this._observers.forEach(observer => {
observer.update(change);
});
}
}
// ConcreteSubject
class ConcreteSubject extends Subject {
constructor() {
super();
this._state = {};
}
get state() {
return this._state;
}
set state(state) {
this._state = state;
this.fire(this._state);
}
}
// Observer
class Observer {
constructor(state) {
this.state = state;
this.initialState = state;
}
update(change) {
let newState = Object.assign({}, this.state, change);
this.state = newState;
}
}
// ConcreteObserver
class ConcreteObserver extends Observer {
constructor(state) {
super(state);
}
update(change) {
super.update(change);
console.log(`ConcreteObserver's new state is ${JSON.stringify(this.state)}`);
}
}
// Usage
let sub = new ConcreteSubject();
let obs1 = new ConcreteObserver({name: 'Observer 1', state: 'active'});
let obs2 = new ConcreteObserver({name: 'Observer 2', state: 'inactive'});
sub.subscribe(obs1);
sub.subscribe(obs2);
sub.state = {name: 'Changed Name', state: 'active'};
// Output:
// ConcreteObserver's new state is {"name":"Changed Name","state":"active"}
// ConcreteObserver's new state is {"name":"Changed Name","state":"active"}
In the example above, the Subject
class is an abstract representation for our concrete subjects. ConcreteSubject
maintains the state and notifies the observers of any change.
The Observer
class provides an update interface that concrete observers (like ConcreteObserver
) will use to update their state when the subject's state changes. The ConcreteObserver
receives these updates, applies them to its state, and logs the change to the console.