Observer Pattern
The Observer pattern (also known as Publish-Subscribe) defines a one-to-many dependency between objects. When one object (the subject) changes state, all its dependents (observers) are notified and updated automatically.
Key Concepts
- Subject - Maintains list of observers and notifies them of changes
- Observer - Defines an update interface for receiving notifications
- Loose Coupling - Subject doesn't need to know specific observer details
- Event-Driven - Changes trigger automatic notifications
Interactive Demo
A live stock ticker that notifies multiple investors when prices change:
Stock Prices
AAPL $150.00
GOOGL $140.00
MSFT $380.00
Investors (Observers)
Notification Log
// Stock market opened. Add investors and change prices to see notifications.
Implementation Examples
Basic Observer Pattern
// Subject (Observable)
class NewsAgency {
constructor() {
this.observers = [];
this.news = null;
}
subscribe(observer) {
this.observers.push(observer);
}
unsubscribe(observer) {
this.observers = this.observers.filter(obs => obs !== observer);
}
notify() {
this.observers.forEach(observer => {
observer.update(this.news);
});
}
publishNews(news) {
this.news = news;
this.notify();
}
}
// Observers
class NewsChannel {
constructor(name) {
this.name = name;
}
update(news) {
console.log(`${this.name} received: ${news}`);
}
}
// Usage
const agency = new NewsAgency();
const cnn = new NewsChannel('CNN');
const bbc = new NewsChannel('BBC');
agency.subscribe(cnn);
agency.subscribe(bbc);
agency.publishNews('Breaking: Observer Pattern Explained!');
// CNN received: Breaking: Observer Pattern Explained!
// BBC received: Breaking: Observer Pattern Explained! Event Emitter (Pub/Sub)
// Modern Event Emitter Pattern
class EventEmitter {
constructor() {
this.events = {};
}
on(event, listener) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(listener);
}
off(event, listener) {
if (!this.events[event]) return;
this.events[event] = this.events[event].filter(l => l !== listener);
}
emit(event, data) {
if (!this.events[event]) return;
this.events[event].forEach(listener => {
listener(data);
});
}
once(event, listener) {
const wrapper = (data) => {
listener(data);
this.off(event, wrapper);
};
this.on(event, wrapper);
}
}
// Usage
const emitter = new EventEmitter();
emitter.on('login', (user) => {
console.log(`User ${user.name} logged in`);
});
emitter.on('login', (user) => {
console.log(`Sending welcome email to ${user.email}`);
});
emitter.emit('login', { name: 'Alice', email: 'alice@example.com' }); Stock Market Observer
// Stock Market Example
class Stock {
constructor(symbol, price) {
this.symbol = symbol;
this.price = price;
this.investors = [];
}
attach(investor) {
this.investors.push(investor);
}
detach(investor) {
this.investors = this.investors.filter(inv => inv !== investor);
}
setPrice(price) {
const oldPrice = this.price;
this.price = price;
const change = ((price - oldPrice) / oldPrice * 100).toFixed(2);
this.investors.forEach(investor => {
investor.notify(this.symbol, price, change);
});
}
}
class Investor {
constructor(name) {
this.name = name;
}
notify(symbol, price, change) {
console.log(
`${this.name} notified: ${symbol} is now $${price} ` +
`(change: ${change}%)`
);
}
}
const tesla = new Stock('TSLA', 200);
const investor1 = new Investor('Warren');
const investor2 = new Investor('Elon');
tesla.attach(investor1);
tesla.attach(investor2);
tesla.setPrice(250); // Both investors get notified Common Use Cases
- UI Event Handling - DOM events, custom events
- Data Binding - Update UI when model changes (MVC, MVVM)
- Real-time Updates - Chat apps, stock tickers, notifications
- State Management - Redux, MobX, Vuex
- Logging - Multiple loggers listening to application events
- Caching - Cache invalidation on data changes
Benefits
- Loose Coupling - Subject and observers are independent
- Dynamic Relationships - Add/remove observers at runtime
- Broadcast Communication - One change notifies many observers
- Open/Closed Principle - Add new observers without modifying subject
Trade-offs
- Unexpected Updates - Hard to track what triggers observer updates
- Memory Leaks - Forgotten observers prevent garbage collection
- Update Storms - Cascade of updates can impact performance
- Ordering Issues - No guaranteed order of notifications
- Debugging - Indirect relationships make debugging harder
Observer in JavaScript
JavaScript has built-in observer patterns:
- EventTarget API - addEventListener/removeEventListener
- MutationObserver - Watch for DOM changes
- IntersectionObserver - Observe element visibility
- ResizeObserver - Monitor element size changes
- Proxy - Intercept and observe object operations