COMP 305: Object-Oriented Software Design

University of San Diego, Fall 2025

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

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

Benefits

Trade-offs

Observer in JavaScript

JavaScript has built-in observer patterns: