Skip to main content

Introduction To The Observer Pattern

The Observer design pattern is a behavioral design pattern that allows you to define or create a subscription mechanism to send notifications to multiple objects about any new events that happen to the object they're observing. The object that is being watched is often called the subject. The objects that are watching the state changes are called observers or listeners.

Two most significant elements

  • Subject: This is the object which is being observed. It maintains a list of observers and provides methods to modify that list.

  • Observer: The objects which are watching the state changes are known as observers or listeners. They provide a method to update that is used by Subject.

Classic Implementation in TypeScript

Here is a basic implementation of the Observer pattern in TypeScript:

interface Observer {
update(subject: Subject): void;
}

class ConcreteObserver implements Observer {
constructor(private id: number) {}

public update(subject: Subject): void {
console.log(
`Observer ${this.id} updated. New state: ${subject.getState()}`
);
}
}

interface Subject {
addObserver(observer: Observer): void;

removeObserver(observer: Observer): void;

notifyObservers(): void;

getState(): number;

setState(state: number): void;
}

class ConcreteSubject implements Subject {
private observers: Observer[] = [];
private state: number = 0;

public addObserver(observer: Observer): void {
const isExist = this.observers.includes(observer);
if (isExist) {
return console.log("Observer has been attached already.");
}

console.log("Attached an observer.");
this.observers.push(observer);
}

public removeObserver(observer: Observer): void {
const observerIndex = this.observers.indexOf(observer);
if (observerIndex === -1) {
return console.log("Nonexistent observer.");
}

this.observers.splice(observerIndex, 1);
console.log("Detached an observer.");
}

public notifyObservers(): void {
console.log("Notifying to all observers...");
this.observers.forEach((observer) => observer.update(this));
}

public getState(): number {
return this.state;
}

public setState(state: number): void {
console.log("Setting state...");
this.state = state;
this.notifyObservers();
}
}

// Client code
const subject = new ConcreteSubject();

const observer1 = new ConcreteObserver(1);
subject.addObserver(observer1);

const observer2 = new ConcreteObserver(2);
subject.addObserver(observer2);

subject.setState(123); // Setting state... and then notifying all observers

In the above example, ConcreteSubject is the subject that is being observed, and ConcreteObserver represents an observer that is watching the state of the ConcreteSubject. When the state of ConcreteSubject changes ( using setState), all registered observers are notified with the new state.

This pattern is commonly used in event handling systems, where the event source acts as the subject, and all the event handlers act as observers.

When To Use The Obsever Pattern

Here are some programming or design "smells" that suggest the Observer pattern would be a good fit as a solution. Here are some code smells or patterns that may lead you to choose the Observer pattern:

  1. Polling: If your code is constantly checking or "polling" an object to see if its state has changed, it could benefit from the Observer pattern. Instead of polling, the Observer pattern allows an object to notify other objects directly when its state changes.

  2. Inefficient Updates: If an object is being updated too often, or if only some of its observers need to react to changes, yet it's updating all of them anyway, that could be inefficient. The Observer pattern can target specific observers, optimizing the update process.

  3. Ineffective Communication Between Objects: If you see objects communicating directly with many other objects to share changes to their internal state, this is a strong smell. This could result in spaghetti code that is difficult to maintain and understand. Implementing the Observer pattern ensures a clean way of communication between objects.

  4. High Component Coupling: If components in your system are highly dependent on each other, then changes in one might affect the others. The Observer pattern provides a way to reduce dependencies between your software components.

Remember, these are just guidelines. Code smells indicate there might be a problem, but it doesn't always mean you need to introduce a design pattern. Often simpler code is more effective than over-engineered solutions. Always consider the complexity and the maintenance cost that a design pattern could add to your software before deciding to implement it.

TypeScript Course Instructor Image
TypeScript Course Instructor Image

Time To Transition From JavaScript To TypeScript

Level Up Your TypeScript And Object Oriented Programming Skills. The only complete TypeScript course on the marketplace you building TypeScript apps like a PRO.

SEE COURSE DETAILS

What Can You Do Next 🙏😊

If you liked the article, consider subscribing to Cloudaffle, my YouTube Channel, where I keep posting in-depth tutorials and all edutainment stuff for ssoftware developers.

YouTube @cloudaffle