Implementing the Observer design pattern in TypeScript

0

A design pattern is a pattern that solves a recurring problem in software design.


The observer model, also known as the publish-subscribe model, is a behavioral model. It allows you to notify multiple objects or subscribers of any event published in the object they observe.

Here you will learn how to implement the watcher design pattern in TypeScript.


The observer model

The watcher model works by defining a one-to-many relationship between the publisher and their subscribers. When an event occurs in the publisher, it notifies all subscribers to that event. A popular example of this pattern is JavaScript event listeners.

For context, let’s say you’re building an inventory tracker that tracks the number of products on your store. In this case, your store is the topic/publisher, and your inventory is the watcher/subscriber. Using the observer design pattern would be optimal in this situation.

In the observer design pattern, your subject class must implement three methods:

  • A join method. This method adds an observer to the subject.
  • A detach method. This method removes an observer from a subject.
  • A notify/update method. This method notifies the subject’s observers when the state changes in the subject.

Your observer class must implement a method, the update method. This method reacts when there is a change in the state of its subject.

Implementing Subject and Observer Classes

The first step in implementing this pattern is to create interfaces for the subject and the watcher class, to ensure that they implement the correct methods:


interface Subject {
attachObserver(observer: Observer): void;
detachObserver(observer: Observer): void;
notifyObserver(): void;
}


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

The interfaces in the code block above define the methods your concrete classes should implement.

A class of concrete subjects

The next step is to implement a concrete subject class that implements the Matter interface:


class Store implements Subject {}

Then initialize the Matterthe state in the Store to classify. Observers of the subject will react to changes in this state.

In this case, the state is a number, and observers will react to an increase in the number:


private numberOfProducts: number;

Next, initialize an array of observers. This table allows you to follow the observers:


private observers: Observer[] = [];

You can find some implementations of the observer pattern using a Set data structure instead of an array to keep track of the observer. Using a set will ensure that the same watcher will not appear twice. If you want to use an array instead, you need to check for duplicate observers in your join method.

Then you need to implement the Matterthe methods of—join, detachand notify/update—in your concrete class.

To implement the join , first check if the observer is already attached and throw an error if so. Alternatively, add the observer to the array using the JavaScript array method, to push:


attachObserver(observer: Observer): void {
const observerExists = this.observers.includes(observer);

if (observerExists) {
throw new Error('Observer has already been subscribed ');
}


this.observers.push(observer);
}

Then implement your detach method by finding the index and removing it from the array using JavaScript splice method.

There may be scenarios where the watcher you are trying to detach has already been detached or was not subscribed in the first place. You should handle these scenarios by adding a conditional statement to check if the observer is in the array or in the set, as appropriate.


detachObserver(observer: Observer): void {
console.log(`Detaching observer ${JSON.stringify(observer)}`);
const observerIndex = this.observers.indexOf(observer);

if (observerIndex === -1) {
throw new Error('Observer does not exist');
}

this.observers.splice(observerIndex, 1);
console.log('Observer detached...');
}

Then implement your notify/update method by looping over your list of watchers and calling the update everyone’s method:


notifyObserver(): void {
console.log('Notifying observers...');

for (const observer of this.observers) {
observer.update(this);
}
}

Finally, for the Matter class, implement a method that manipulates state, then notifies observers of the change by calling their notify/update method. This example is a simplification of how a subject can perform an action and then inform observers:


newProduct(products: number): void {
this.numberOfProducts += products;
console.log('New product added to the store');
this.notifyObserver();
}

Concrete classes of observers

Create one or more watcher classes to subscribe to the publisher. Every watcher class must implement the Observer interface.

Observer classes will implement a notify/update method that only the subject they are observing should invoke. This method should contain all the business logic you need to execute in response to a subject state change:


class Inventory implements Observer {
update(): void {
console.log('New product added to the store, updating inventory...');
}
}


class Customer implements Observer {
update(): void {
console.log('New product added to the store, I have to go check it out...');
}
}

Using the Observer Model

To use this pattern, instantiate the concrete subject and observer classes. Once you’ve done that, call the Subject join method and pass the Observer instance as an argument. In response, the subject will add this instance to its watch list:


const store = new Store();
const inventory = new Inventory();
const customer = new Customer()


store.attachObserver(inventory);
store.attachObserver(customer);
store.newProduct(30);

This code simulates a state change. The change will trigger the notification method on the Matter to classify. This method, in turn, calls the notify method on each of its observers. Each observer will then execute its own business logic.

You should only use this pattern when state changes of one object affect other objects and the set of objects involved is unknown or dynamic.

Advantages of using the observer model

Using this pattern in your code allows you to maintain the open/close principle. You can add as many subscribers as you want and establish relationships between objects at runtime, without modifying the topic code.

Share.

Comments are closed.