Skip to content

Observer Pattern

Category: Behavioral Pattern

Overview

Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically. This pattern is commonly used to implement distributed event handling systems and forms the basis for the model-view-controller (MVC) architectural pattern.

Usage Guidelines

Use when:

  • Object state changes need to trigger updates in other objects
  • One object (subject) needs to notify many objects (observers)
  • Subject and observers should be loosely coupled
  • Set of observers can change at runtime

Avoid when:

  • Direct method calls suffice for simple scenarios
  • Notification overhead is unacceptable for performance
  • Risk of memory leaks from forgotten observer references
  • Specific notification order is critical

Implementation

from __future__ import annotations
from abc import ABC, abstractmethod

class Observer(ABC):
    """Abstract base class for observers."""

    @abstractmethod
    def update(self, subject: Subject) -> None:
        """Receive update from subject.

        Args:
            subject: The subject that triggered the update.
        """
        pass

class Subject(ABC):
    """Abstract base class for subjects being observed."""

    def __init__(self) -> None:
        """Initialize an empty list of observers."""
        self._observers: list[Observer] = []

    def attach(self, observer: Observer) -> None:
        """Attach an observer to the subject.

        Args:
            observer: The observer to attach.
        """
        if observer not in self._observers:
            self._observers.append(observer)

    def detach(self, observer: Observer) -> None:
        """Detach an observer from the subject.

        Args:
            observer: The observer to detach.
        """
        if observer in self._observers:
            self._observers.remove(observer)

    def notify(self) -> None:
        """Notify all observers of a state change."""
        for observer in self._observers:
            observer.update(self)

class WeatherStation(Subject):
    """Concrete subject representing a weather station.

    Tracks weather data and notifies observers when it changes.
    """

    def __init__(self) -> None:
        """Initialize the weather station."""
        super().__init__()
        self._temperature: float = 0.0
        self._humidity: float = 0.0
        self._pressure: float = 0.0

    def set_measurements(
        self,
        temperature: float,
        humidity: float,
        pressure: float
    ) -> None:
        """Set weather measurements and notify observers.

        Args:
            temperature: Temperature in Celsius.
            humidity: Humidity percentage.
            pressure: Atmospheric pressure in hPa.
        """
        self._temperature = temperature
        self._humidity = humidity
        self._pressure = pressure
        self.notify()

    def get_temperature(self) -> float:
        """Get current temperature."""
        return self._temperature

    def get_humidity(self) -> float:
        """Get current humidity."""
        return self._humidity

    def get_pressure(self) -> float:
        """Get current pressure."""
        return self._pressure

class PhoneDisplay(Observer):
    """Concrete observer that displays weather on a phone."""

    def __init__(self) -> None:
        """Initialize the phone display."""
        self._temperature: float = 0.0

    def update(self, subject: Subject) -> None:
        """Update display with new weather data.

        Args:
            subject: The weather station subject.
        """
        if isinstance(subject, WeatherStation):
            self._temperature = subject.get_temperature()

    def display(self) -> str:
        """Get display text."""
        return f"Phone Display: Temperature is {self._temperature}°C"

Usage

# Create weather station and displays
weather_station = WeatherStation()
phone_display = PhoneDisplay()

# Register observers
weather_station.attach(phone_display)

# Update weather data - all observers notified automatically
weather_station.set_measurements(25.5, 65, 1013)

print(phone_display.display())  # Phone Display: Temperature is 25.5°C

# Unregister an observer
weather_station.detach(phone_display)

Trade-offs

Benefits:

  1. Loose coupling between subject and observers
  2. Can add/remove observers at runtime dynamically
  3. One state change notifies multiple observers through broadcast
  4. Add new observers without modifying subject (Open/Closed Principle)

Drawbacks:

  1. Observers may be updated in unexpected order
  2. Forgotten observer references can cause memory leaks
  3. Notifying many observers can be slow
  4. Notification chains can be hard to debug

Real-World Examples

  • GUI event systems for button clicks, mouse movements, keyboard events
  • MVC frameworks where models notify views of data changes
  • Stock market applications with price changes notifying multiple displays
  • Social media feeds with new posts notifying followers
  • Mediator
  • Singleton
  • Command
  • State
  • Pub-Sub

API Reference

design_patterns.behavioral.observer

Observer Pattern Module

The Observer pattern defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically. This pattern is commonly used to implement distributed event handling systems and is the basis for the model-view-controller (MVC) architectural pattern.

Example

Observing weather station data:

weather_station = WeatherStation()
phone_display = PhoneDisplay()
tv_display = TVDisplay()

weather_station.attach(phone_display)
weather_station.attach(tv_display)

weather_station.set_temperature(25.5)  # Notifies all observers

Newsletter

Bases: Subject

Concrete subject representing a newsletter.

Notifies subscribers when new articles are published.

Source code in src/design_patterns/behavioral/observer.py
class Newsletter(Subject):
    """Concrete subject representing a newsletter.

    Notifies subscribers when new articles are published.
    """

    def __init__(self, name: str) -> None:
        """Initialize the newsletter.

        Args:
            name: Newsletter name.
        """
        super().__init__()
        self.name = name
        self._articles: list[str] = []

    def publish_article(self, article: str) -> None:
        """Publish a new article and notify subscribers.

        Args:
            article: The article content or title.
        """
        self._articles.append(article)
        self.notify()

    def get_latest_article(self) -> str:
        """Get the most recent article.

        Returns:
            The latest article.
        """
        return self._articles[-1] if self._articles else ""

    def get_article_count(self) -> int:
        """Get the total number of published articles.

        Returns:
            Number of articles.
        """
        return len(self._articles)

__init__(name)

Initialize the newsletter.

Parameters:

Name Type Description Default
name str

Newsletter name.

required
Source code in src/design_patterns/behavioral/observer.py
def __init__(self, name: str) -> None:
    """Initialize the newsletter.

    Args:
        name: Newsletter name.
    """
    super().__init__()
    self.name = name
    self._articles: list[str] = []

get_article_count()

Get the total number of published articles.

Returns:

Type Description
int

Number of articles.

Source code in src/design_patterns/behavioral/observer.py
def get_article_count(self) -> int:
    """Get the total number of published articles.

    Returns:
        Number of articles.
    """
    return len(self._articles)

get_latest_article()

Get the most recent article.

Returns:

Type Description
str

The latest article.

Source code in src/design_patterns/behavioral/observer.py
def get_latest_article(self) -> str:
    """Get the most recent article.

    Returns:
        The latest article.
    """
    return self._articles[-1] if self._articles else ""

publish_article(article)

Publish a new article and notify subscribers.

Parameters:

Name Type Description Default
article str

The article content or title.

required
Source code in src/design_patterns/behavioral/observer.py
def publish_article(self, article: str) -> None:
    """Publish a new article and notify subscribers.

    Args:
        article: The article content or title.
    """
    self._articles.append(article)
    self.notify()

NewsletterSubscriber

Bases: Observer

Concrete observer representing a newsletter subscriber.

Source code in src/design_patterns/behavioral/observer.py
class NewsletterSubscriber(Observer):
    """Concrete observer representing a newsletter subscriber."""

    def __init__(self, email: str) -> None:
        """Initialize the subscriber.

        Args:
            email: Subscriber email address.
        """
        self.email = email
        self.messages: list[str] = []

    def update(self, subject: Subject) -> None:
        """Receive notification.

        Args:
            subject: The subject sending the notification.
        """
        if isinstance(subject, Newsletter):
            self.messages.append(subject.get_latest_article())

__init__(email)

Initialize the subscriber.

Parameters:

Name Type Description Default
email str

Subscriber email address.

required
Source code in src/design_patterns/behavioral/observer.py
def __init__(self, email: str) -> None:
    """Initialize the subscriber.

    Args:
        email: Subscriber email address.
    """
    self.email = email
    self.messages: list[str] = []

update(subject)

Receive notification.

Parameters:

Name Type Description Default
subject Subject

The subject sending the notification.

required
Source code in src/design_patterns/behavioral/observer.py
def update(self, subject: Subject) -> None:
    """Receive notification.

    Args:
        subject: The subject sending the notification.
    """
    if isinstance(subject, Newsletter):
        self.messages.append(subject.get_latest_article())

Observer

Bases: ABC

Abstract base class for observers.

Source code in src/design_patterns/behavioral/observer.py
class Observer(ABC):
    """Abstract base class for observers."""

    @abstractmethod
    def update(self, subject: Subject) -> None:
        """Receive update from subject.

        Args:
            subject: The subject that triggered the update.
        """
        pass

update(subject) abstractmethod

Receive update from subject.

Parameters:

Name Type Description Default
subject Subject

The subject that triggered the update.

required
Source code in src/design_patterns/behavioral/observer.py
@abstractmethod
def update(self, subject: Subject) -> None:
    """Receive update from subject.

    Args:
        subject: The subject that triggered the update.
    """
    pass

PhoneDisplay

Bases: Observer

Concrete observer that displays weather on a phone.

Source code in src/design_patterns/behavioral/observer.py
class PhoneDisplay(Observer):
    """Concrete observer that displays weather on a phone."""

    def __init__(self) -> None:
        """Initialize the phone display."""
        self._temperature: float = 0.0
        self.update_count: int = 0

    def update(self, subject: Subject) -> None:
        """Update display with new weather data.

        Args:
            subject: The weather station subject.
        """
        if isinstance(subject, WeatherStation):
            self._temperature = subject.get_temperature()
            self.update_count += 1

    def display(self) -> str:
        """Get display text.

        Returns:
            Current temperature display text.
        """
        return f"Phone Display: Temperature is {self._temperature}°C"

__init__()

Initialize the phone display.

Source code in src/design_patterns/behavioral/observer.py
def __init__(self) -> None:
    """Initialize the phone display."""
    self._temperature: float = 0.0
    self.update_count: int = 0

display()

Get display text.

Returns:

Type Description
str

Current temperature display text.

Source code in src/design_patterns/behavioral/observer.py
def display(self) -> str:
    """Get display text.

    Returns:
        Current temperature display text.
    """
    return f"Phone Display: Temperature is {self._temperature}°C"

update(subject)

Update display with new weather data.

Parameters:

Name Type Description Default
subject Subject

The weather station subject.

required
Source code in src/design_patterns/behavioral/observer.py
def update(self, subject: Subject) -> None:
    """Update display with new weather data.

    Args:
        subject: The weather station subject.
    """
    if isinstance(subject, WeatherStation):
        self._temperature = subject.get_temperature()
        self.update_count += 1

Subject

Bases: ABC

Abstract base class for subjects being observed.

Source code in src/design_patterns/behavioral/observer.py
class Subject(ABC):
    """Abstract base class for subjects being observed."""

    def __init__(self) -> None:
        """Initialize an empty list of observers."""
        self._observers: list[Observer] = []

    def attach(self, observer: Observer) -> None:
        """Attach an observer to the subject.

        Args:
            observer: The observer to attach.
        """
        if observer not in self._observers:
            self._observers.append(observer)

    def detach(self, observer: Observer) -> None:
        """Detach an observer from the subject.

        Args:
            observer: The observer to detach.
        """
        if observer in self._observers:
            self._observers.remove(observer)

    def notify(self) -> None:
        """Notify all observers of a state change."""
        for observer in self._observers:
            observer.update(self)

__init__()

Initialize an empty list of observers.

Source code in src/design_patterns/behavioral/observer.py
def __init__(self) -> None:
    """Initialize an empty list of observers."""
    self._observers: list[Observer] = []

attach(observer)

Attach an observer to the subject.

Parameters:

Name Type Description Default
observer Observer

The observer to attach.

required
Source code in src/design_patterns/behavioral/observer.py
def attach(self, observer: Observer) -> None:
    """Attach an observer to the subject.

    Args:
        observer: The observer to attach.
    """
    if observer not in self._observers:
        self._observers.append(observer)

detach(observer)

Detach an observer from the subject.

Parameters:

Name Type Description Default
observer Observer

The observer to detach.

required
Source code in src/design_patterns/behavioral/observer.py
def detach(self, observer: Observer) -> None:
    """Detach an observer from the subject.

    Args:
        observer: The observer to detach.
    """
    if observer in self._observers:
        self._observers.remove(observer)

notify()

Notify all observers of a state change.

Source code in src/design_patterns/behavioral/observer.py
def notify(self) -> None:
    """Notify all observers of a state change."""
    for observer in self._observers:
        observer.update(self)

TVDisplay

Bases: Observer

Concrete observer that displays weather on a TV.

Source code in src/design_patterns/behavioral/observer.py
class TVDisplay(Observer):
    """Concrete observer that displays weather on a TV."""

    def __init__(self) -> None:
        """Initialize the TV display."""
        self._temperature: float = 0.0
        self._humidity: float = 0.0
        self.update_count: int = 0

    def update(self, subject: Subject) -> None:
        """Update display with new weather data.

        Args:
            subject: The weather station subject.
        """
        if isinstance(subject, WeatherStation):
            self._temperature = subject.get_temperature()
            self._humidity = subject.get_humidity()
            self.update_count += 1

    def display(self) -> str:
        """Get display text.

        Returns:
            Current weather display text.
        """
        return (f"TV Display: Temperature is {self._temperature}°C, "
                f"Humidity is {self._humidity}%")

__init__()

Initialize the TV display.

Source code in src/design_patterns/behavioral/observer.py
def __init__(self) -> None:
    """Initialize the TV display."""
    self._temperature: float = 0.0
    self._humidity: float = 0.0
    self.update_count: int = 0

display()

Get display text.

Returns:

Type Description
str

Current weather display text.

Source code in src/design_patterns/behavioral/observer.py
def display(self) -> str:
    """Get display text.

    Returns:
        Current weather display text.
    """
    return (f"TV Display: Temperature is {self._temperature}°C, "
            f"Humidity is {self._humidity}%")

update(subject)

Update display with new weather data.

Parameters:

Name Type Description Default
subject Subject

The weather station subject.

required
Source code in src/design_patterns/behavioral/observer.py
def update(self, subject: Subject) -> None:
    """Update display with new weather data.

    Args:
        subject: The weather station subject.
    """
    if isinstance(subject, WeatherStation):
        self._temperature = subject.get_temperature()
        self._humidity = subject.get_humidity()
        self.update_count += 1

WeatherStation

Bases: Subject

Concrete subject representing a weather station.

Tracks weather data and notifies observers when it changes.

Source code in src/design_patterns/behavioral/observer.py
class WeatherStation(Subject):
    """Concrete subject representing a weather station.

    Tracks weather data and notifies observers when it changes.
    """

    def __init__(self) -> None:
        """Initialize the weather station."""
        super().__init__()
        self._temperature: float = 0.0
        self._humidity: float = 0.0
        self._pressure: float = 0.0

    def set_measurements(
        self,
        temperature: float,
        humidity: float,
        pressure: float
    ) -> None:
        """Set weather measurements and notify observers.

        Args:
            temperature: Temperature in Celsius.
            humidity: Humidity percentage.
            pressure: Atmospheric pressure in hPa.
        """
        self._temperature = temperature
        self._humidity = humidity
        self._pressure = pressure
        self.notify()

    def set_temperature(self, temperature: float) -> None:
        """Set temperature and notify observers.

        Args:
            temperature: Temperature in Celsius.
        """
        self._temperature = temperature
        self.notify()

    def get_temperature(self) -> float:
        """Get current temperature.

        Returns:
            Current temperature.
        """
        return self._temperature

    def get_humidity(self) -> float:
        """Get current humidity.

        Returns:
            Current humidity.
        """
        return self._humidity

    def get_pressure(self) -> float:
        """Get current pressure.

        Returns:
            Current pressure.
        """
        return self._pressure

__init__()

Initialize the weather station.

Source code in src/design_patterns/behavioral/observer.py
def __init__(self) -> None:
    """Initialize the weather station."""
    super().__init__()
    self._temperature: float = 0.0
    self._humidity: float = 0.0
    self._pressure: float = 0.0

get_humidity()

Get current humidity.

Returns:

Type Description
float

Current humidity.

Source code in src/design_patterns/behavioral/observer.py
def get_humidity(self) -> float:
    """Get current humidity.

    Returns:
        Current humidity.
    """
    return self._humidity

get_pressure()

Get current pressure.

Returns:

Type Description
float

Current pressure.

Source code in src/design_patterns/behavioral/observer.py
def get_pressure(self) -> float:
    """Get current pressure.

    Returns:
        Current pressure.
    """
    return self._pressure

get_temperature()

Get current temperature.

Returns:

Type Description
float

Current temperature.

Source code in src/design_patterns/behavioral/observer.py
def get_temperature(self) -> float:
    """Get current temperature.

    Returns:
        Current temperature.
    """
    return self._temperature

set_measurements(temperature, humidity, pressure)

Set weather measurements and notify observers.

Parameters:

Name Type Description Default
temperature float

Temperature in Celsius.

required
humidity float

Humidity percentage.

required
pressure float

Atmospheric pressure in hPa.

required
Source code in src/design_patterns/behavioral/observer.py
def set_measurements(
    self,
    temperature: float,
    humidity: float,
    pressure: float
) -> None:
    """Set weather measurements and notify observers.

    Args:
        temperature: Temperature in Celsius.
        humidity: Humidity percentage.
        pressure: Atmospheric pressure in hPa.
    """
    self._temperature = temperature
    self._humidity = humidity
    self._pressure = pressure
    self.notify()

set_temperature(temperature)

Set temperature and notify observers.

Parameters:

Name Type Description Default
temperature float

Temperature in Celsius.

required
Source code in src/design_patterns/behavioral/observer.py
def set_temperature(self, temperature: float) -> None:
    """Set temperature and notify observers.

    Args:
        temperature: Temperature in Celsius.
    """
    self._temperature = temperature
    self.notify()