At the Jurassic Park control center, Robert Muldoon observes dozens of monitors simultaneously. "The problem is," he explains, "that every system — fences, cameras, motion sensors — must immediately react to changes in a dinosaur's status. But they can't keep polling (checking) the database all the time!" The solution is the Observer pattern — a mechanism where objects automatically notify interested parties about changes.
Without the Observer pattern, components must directly know and call each other:
1// Problem: the alarm system knows ALL other systems
2function onDinosaurEscaped(dinosaur) {
3 // Must manually call each system
4 fenceSystem.activateEmergency(dinosaur);
5 cameraSystem.trackDinosaur(dinosaur);
6 alarmSystem.soundAlert(dinosaur);
7 gateSystem.lockAllGates();
8 evacuationSystem.startEvacuation();
9 // What if we add a new system? We have to modify this function!
10}Each new system requires modifying existing code. This violates the Open-Closed Principle.
In the Observer pattern we have two types of objects:
1// Simple EventEmitter — the heart of the Observer pattern
2class EventEmitter {
3 constructor() {
4 this.listeners = {};
5 }
6
7 // Register an observer (subscribe)
8 on(event, callback) {
9 if (!this.listeners[event]) {
10 this.listeners[event] = [];
11 }
12 this.listeners[event].push(callback);
13 return this; // Enables chaining
14 }
15
16 // Emit an event (notify observers)
17 emit(event, ...args) {
18 const callbacks = this.listeners[event];
19 if (callbacks) {
20 callbacks.forEach(callback => callback(...args));
21 }
22 return this;
23 }
24
25 // Remove an observer
26 off(event, callback) {
27 if (this.listeners[event]) {
28 this.listeners[event] = this.listeners[event].filter(
29 cb => cb !== callback
30 );
31 }
32 return this;
33 }
34
35 // One-time subscription
36 once(event, callback) {
37 const wrapper = (...args) => {
38 callback(...args);
39 this.off(event, wrapper);
40 };
41 this.on(event, wrapper);
42 return this;
43 }
44}1// Dinosaur monitoring system based on EventEmitter
2class DinosaurMonitor extends EventEmitter {
3 constructor() {
4 super();
5 this.dinosaurs = new Map();
6 }
7
8 registerDinosaur(id, data) {
9 this.dinosaurs.set(id, { ...data, status: "contained" });
10 this.emit("dinosaur:registered", { id, ...data });
11 }
12
13 updateStatus(id, newStatus) {
14 const dino = this.dinosaurs.get(id);
15 if (!dino) return;
16
17 const oldStatus = dino.status;
18 dino.status = newStatus;
19
20 this.emit("status:changed", { id, name: dino.name, oldStatus, newStatus });
21
22 // Emit a special event for escapes
23 if (newStatus === "escaped") {
24 this.emit("dinosaur:escaped", { id, name: dino.name, species: dino.species });
25 }
26 }
27}
28
29// Creating the system
30const monitor = new DinosaurMonitor();
31
32// Registering observers — each system connects itself
33monitor.on("dinosaur:escaped", (data) => {
34 console.log(`FENCE ALARM: ${data.name} escaped! Activating electric fence.`);
35});
36
37monitor.on("dinosaur:escaped", (data) => {
38 console.log(`CAMERA ALARM: Tracking ${data.name} (${data.species}) on cameras.`);
39});
40
41monitor.on("dinosaur:escaped", (data) => {
42 console.log(`EVACUATION ALARM: Starting evacuation due to ${data.name} escape!`);
43});
44
45monitor.on("status:changed", (data) => {
46 console.log(`LOG: ${data.name} changed status from "${data.oldStatus}" to "${data.newStatus}"`);
47});
48
49// Register and simulate
50monitor.registerDinosaur("TREX-001", { name: "Rexy", species: "T-Rex" });
51monitor.registerDinosaur("RAPTOR-01", { name: "Blue", species: "Velociraptor" });
52
53// Simulate escape
54monitor.updateStatus("TREX-001", "escaped");
55// LOG: Rexy changed status from "contained" to "escaped"
56// FENCE ALARM: Rexy escaped! Activating electric fence.
57// CAMERA ALARM: Tracking Rexy (T-Rex) on cameras.
58// EVACUATION ALARM: Starting evacuation due to Rexy escape!Pub/Sub extends the Observer pattern where communication happens through channels/topics instead of direct references:
1class PubSub {
2 constructor() {
3 this.topics = {};
4 this.subscriptionId = 0;
5 }
6
7 // Subscribe to a topic
8 subscribe(topic, callback) {
9 if (!this.topics[topic]) {
10 this.topics[topic] = {};
11 }
12
13 const id = ++this.subscriptionId;
14 this.topics[topic][id] = callback;
15
16 // Return an unsubscribe function
17 return () => {
18 delete this.topics[topic][id];
19 };
20 }
21
22 // Publish a message
23 publish(topic, data) {
24 if (!this.topics[topic]) return;
25
26 Object.values(this.topics[topic]).forEach(callback => {
27 callback(data);
28 });
29 }
30}
31
32// Central park communication system
33const parkBus = new PubSub();
34
35// Systems subscribe to topics they care about
36const unsubFence = parkBus.subscribe("security:alert", (data) => {
37 console.log(`Fence: Alert received — ${data.message}`);
38});
39
40parkBus.subscribe("security:alert", (data) => {
41 console.log(`Command center: Level ${data.level} alert — ${data.message}`);
42});
43
44parkBus.subscribe("feeding:schedule", (data) => {
45 console.log(`Kitchen: Prepare ${data.food} for ${data.dinosaur}`);
46});
47
48// Publishing events — the sender doesn't know who is listening
49parkBus.publish("security:alert", {
50 level: "HIGH",
51 message: "Movement detected near sector C fence"
52});
53
54parkBus.publish("feeding:schedule", {
55 dinosaur: "Rexy",
56 food: "200kg beef",
57 time: "14:00"
58});
59
60// Unsubscribe
61unsubFence(); // Fence stops listening1class ParkNotificationSystem extends EventEmitter {
2 constructor() {
3 super();
4 this.history = [];
5 }
6
7 notify(channel, message, priority = "normal") {
8 const notification = {
9 channel,
10 message,
11 priority,
12 timestamp: new Date().toISOString()
13 };
14
15 this.history.push(notification);
16 this.emit(channel, notification);
17 this.emit("*", notification); // Wildcard — all notifications
18 }
19
20 getHistory(channel) {
21 if (channel) {
22 return this.history.filter(n => n.channel === channel);
23 }
24 return [...this.history];
25 }
26}
27
28const notifications = new ParkNotificationSystem();
29
30// Admin panel — listens to everything
31notifications.on("*", (n) => {
32 console.log(`[ADMIN] [${n.priority.toUpperCase()}] ${n.channel}: ${n.message}`);
33});
34
35// Security system — only alerts
36notifications.on("security", (n) => {
37 if (n.priority === "critical") {
38 console.log(`RED ALARM: ${n.message}`);
39 }
40});
41
42// Usage
43notifications.notify("security", "Camera failure in sector B", "normal");
44notifications.notify("security", "T-Rex broke through fence!", "critical");
45notifications.notify("maintenance", "Replace lab light bulbs", "low");
46
47console.log("Alert history:", notifications.getHistory("security"));| Feature | Observer | Pub/Sub | |---------|----------|---------| | Coupling | Subject knows observers | Sender and receiver don't know each other | | Communication | Direct | Through channel/topic | | Use when | One object notifies a few | Many objects communicate loosely | | Example | Form notifying validators | Microservices exchanging messages |
Muldoon summarizes: "The Observer pattern is like the park's alarm system — you don't have to constantly check every sensor. Sensors will notify you themselves:"
on(), emit(), off() methodsThese patterns are the foundation of many libraries and frameworks — from Node.js (EventEmitter) through React (state management) to microservice architectures.