Behavioral · DWG NO. 003

Observer

Let one subject broadcast state changes to any number of listeners, without knowing who they are.

TypeBehavioral
ScopeOne-to-many
ComplexityLow
Common inUI events, pub/sub, reactive stores

The subject shouldn't have to import every listener

A shopping cart needs to update the header badge, recalculate tax, and log analytics whenever an item is added. Wiring all three directly into the cart's `addItem` method couples unrelated features together and means the cart class grows every time a new feature wants to react to it.

Subscribe, then broadcast

The subject keeps a list of observers and exposes `subscribe` / `unsubscribe`. When its state changes it loops the list and calls each observer's `update`. Observers only need to implement that one method — the subject never knows what they do with the notification.

Cart (subject) notify() Header badge Tax calc Analytics

A cart subject with three observers

Each observer is just a function. Adding analytics later is a one-line `subscribe` call — the `Cart` class itself never changes.

cart-observer.js
class Cart {
  #items = [];
  #observers = new Set();

  subscribe(observer) {
    this.#observers.add(observer);
    return () => this.#observers.delete(observer); // unsubscribe handle
  }

  addItem(item) {
    this.#items.push(item);
    for (const observer of this.#observers) {
      observer({ type: "item-added", item, total: this.#items.length });
    }
  }
}

const cart = new Cart();

cart.subscribe((e) => console.log(`badge: ${e.total} items`));
cart.subscribe((e) => console.log(`tax: recalculating for ${e.item}`));
const stopAnalytics = cart.subscribe((e) => console.log(`analytics: logged ${e.type}`));

cart.addItem("keyboard");
stopAnalytics(); // analytics observer stops listening
cart.addItem("mouse");

Trade-offs