Observer Pattern in JavaScript

In this article we will explore the Observer Pattern in JavaScript. We will look at a real world example that involves processing order shipment and how to implement observer pattern in that context.

In object oriented programming the Observer Pattern defines a one-to-many dependency between objects, where one object (the subject or observable) maintains a list of dependents (observers) that are notified of any changes in the subject’s state, typically by calling one of their methods.

Let’s take a look at the order shipment diagram from the Strategy Pattern video. We will continue working with the order shipping example. So, please, check it out that video.

Observer Pattern Diagrams

In the diagram above the tracking number notification logic is a part of order shipping logic. However, it may be a good idea to decouple order shipping service and tracking information notification service.

Now let’s look at the class diagram for the Observer Pattern.

In the above diagram order shipping service notifies tracking information notification service that order has been shipped. There maybe some other services that need to be notified that order is shipped, such as client billing service (if it is a drop shipper system) and, maybe inventory service.

Now let’s look at the class diagram for the Observer Pattern.

We have Observable interface that represents the subject that is being observed. It maintains a list of its dependents, the observers. The subject provides methods to add, remove, or notify observers.

Observer interface defines update() method. Abstract ShippingServiceObserver class implements this method to respond to changes in the subject’s state. In its constructor, has logic that registers observer with the observable.

ShippingService is a concrete subject class that implements the observable. It holds the actual state and triggers notifications to observers when the state changes.

TrackingInfoSenderService concrete observer extends ShippingServerObserver and receives the actual notifications.

Implementation

In our project, in src folder let’s create observerPattern folder.

First we define types and interfaces in types.d.ts and interfaces.d.ts files

// src/observerPattern/types.d.ts

type Shipment = {
  trackingNumber: string;
}
// src/observerPattern/interfaces.d.ts

interface Observable {
  attach(observer: Observer);
  detach(observer: Observer);
  notify();
}

interface Observer {
  update(observable: Observable);
}

Next, let’s create ShippingService class.

// src/observerPattern/ShippingService.ts

import { Order } from "../strategyPattern/order.js";

export default class ShippingService implements Observable {
  private observers: Array<Observer> = [];
  private order: Order;

  constructor(order: Order) {
    this.order = order;
  }

  getOrder() {
    return this.order;
  }

  createShipment(shipment: Shipment) {
    this.order.addShipment(shipment);
    this.notify();
  }

  attach(observer: Observer) {
    this.observers.push(observer);
  }

  detach(observer: Observer) {
    this.observers = this.observers.filter(
      (item: Observer) => item !== observer
    );
  }

  notify() {
    this.observers.forEach((item: Observer) => item.update(this));
  }
}

This class implements Observable interface with attach() detach() and notify() methods. It also requires Order when instantiated.

Now, let’s create ShippingServiceObserver.

// src/observerPattern/ShippingServiceObserver.ts

import ShippingService from "./ShippingService.js";

export default abstract class ShippingServiceObserver implements Observer {
  protected shippingService: ShippingService;

  constructor(shippingService: ShippingService) {
    this.shippingService = shippingService;
    this.shippingService.attach(this);
  }

  update(observable: Observable) {
    if (observable === this.shippingService) {
      this.doUpdate(this.shippingService);
    }
  }

  abstract doUpdate(shippingService: ShippingService): void;
}

This is an abstract class. Classes that extend this class will be attached to ShippingService to observe it. Also, those classes will have to implement doUpdate() method with ShippingService passed as a parameter.

Finally, let’s create index file where we put the observer together.

// src/observerPattern/index.ts

import { Order, OrderSource } from "../strategyPattern/order.js";
import TrackingInfoSender from "../strategyPattern/trackingInfo.js";
import ShippingServiceObserver from "./ShippingServiceObserver.js";
import ShippingService from "./ShippingService.js";

class TrackingInfoSenderService extends ShippingServiceObserver {
  doUpdate(shippingService: ShippingService): void {
    const order = shippingService.getOrder();
    const trackingInfoSender = TrackingInfoSender.getSender(order);
    trackingInfoSender.sendTracking();
  }
}

export default function runObserverPattern() {
  const order = new Order(OrderSource.BigCommerce);
  const shippingService = new ShippingService(order);
  new TrackingInfoSenderService(shippingService);
  shippingService.createShipment({ trackingNumber: "123456" });
}

We define TrackingInfoSenderService class that extends ShippingServiceObserver. The doUpdate() method gets an order form ShippingService and uses the Strategy Pattern to send tracking information to an appropriate order soruce.

runObserverPattern() function runs the Observer Pattern code. It instantiate order from BigCommerce as a source, creates shipping and tracking info sender services and then creates shipment. When shipment is created, tracking info sender service is notified, and it sends the tracking information to the appropriate source.

Conclusion

In conclusion, the Observer Pattern emerges as a powerful solution in JavaScript, offering an elegant and efficient means of managing communication between objects. By facilitating a clear separation between the observer (subscriber) and the subject (publisher), this pattern promotes flexibility, maintainability, and scalability in code architecture. As we explored the intricacies of implementation and witnessed real-world applications, it becomes evident that the Observer Pattern is not just a tool; it’s a strategic asset for creating dynamic, responsive, and organized JavaScript applications. Use the observer pattern, and elevate your coding practices to new heights. Happy coding!

Share this article

Posted

in

by