Let one subject broadcast state changes to any number of listeners, without knowing who they are.
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.
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.
Each observer is just a function. Adding analytics later is a one-line `subscribe` call — the `Cart` class itself never changes.
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");