👀 Observer Pattern Tutorial

The Observer Pattern is a behavioral design pattern that defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

🔍 When to Use It?

  • When changes to one object require changing others, and you don’t know how many objects need to change
  • When an object should be able to notify other objects without making assumptions about who these objects are
  • When you need a publish-subscribe model where publishers send messages to subscribers

🧩 Scenario

Imagine a weather monitoring application:

  • A weather station (Subject) collects data about temperature, humidity, and pressure
  • Multiple displays (Observers) show different visualizations of this data
  • When weather data changes, all displays should update automatically

🧱 Components of the Observer Pattern

Subject: The object that has a state and notifies observers when it changes

Observer: An interface defining the update method that subjects call

ConcreteSubject: Implements the Subject interface and maintains state

ConcreteObserver: Implements the Observer interface to respond to subject updates

classDiagram
    direction LR

    class Subject {
        <<interface>>
        +registerObserver(observer)
        +removeObserver(observer)
        +notifyObservers()
    }

    class Observer {
        <<interface>>
        +update(data)
    }

    class ConcreteSubject {
        -observers: List
        -state
        +registerObserver(observer)
        +removeObserver(observer)
        +notifyObservers()
        +setState(newState)
    }

    class ConcreteObserver {
        -subject: Subject
        +update(data)
    }

    Subject <|-- ConcreteSubject
    Observer <|-- ConcreteObserver
    ConcreteSubject --> Observer : notifies
    ConcreteObserver --> ConcreteSubject : observes

💻 Implementation

Let’s implement our weather station example:

Observer Interface

from abc import ABC, abstractmethod

class Observer(ABC):
    @abstractmethod
    def update(self, temperature, humidity, pressure):
        pass

Subject Interface

from abc import ABC, abstractmethod

class Subject(ABC):
    @abstractmethod
    def register_observer(self, observer):
        pass
    
    @abstractmethod
    def remove_observer(self, observer):
        pass
    
    @abstractmethod
    def notify_observers(self):
        pass

Concrete Subject: WeatherData

class WeatherData(Subject):
    def __init__(self):
        self.observers = []
        self.temperature = 0
        self.humidity = 0
        self.pressure = 0
    
    def register_observer(self, observer):
        if observer not in self.observers:
            self.observers.append(observer)
    
    def remove_observer(self, observer):
        if observer in self.observers:
            self.observers.remove(observer)
    
    def notify_observers(self):
        for observer in self.observers:
            observer.update(self.temperature, self.humidity, self.pressure)
    
    def set_measurements(self, temperature, humidity, pressure):
        self.temperature = temperature
        self.humidity = humidity
        self.pressure = pressure
        self.notify_observers()

Concrete Observers: Display Elements

class CurrentConditionsDisplay(Observer):
    def __init__(self, weather_data):
        self.temperature = 0
        self.humidity = 0
        weather_data.register_observer(self)
    
    def update(self, temperature, humidity, pressure):
        self.temperature = temperature
        self.humidity = humidity
        self.display()
    
    def display(self):
        print(f"Current conditions: {self.temperature}°C and {self.humidity}% humidity")

class StatisticsDisplay(Observer):
    def __init__(self, weather_data):
        self.temperatures = []
        weather_data.register_observer(self)
    
    def update(self, temperature, humidity, pressure):
        self.temperatures.append(temperature)
        self.display()
    
    def display(self):
        avg = sum(self.temperatures) / len(self.temperatures)
        print(f"Avg/Max/Min temperature: {avg:.1f}/{max(self.temperatures)}/{min(self.temperatures)}")

Client Code

# Create the WeatherData subject
weather_data = WeatherData()

# Create observers
current_display = CurrentConditionsDisplay(weather_data)
statistics_display = StatisticsDisplay(weather_data)

# Simulate new weather measurements
weather_data.set_measurements(25, 65, 1013)
# Output:
# Current conditions: 25°C and 65% humidity
# Avg/Max/Min temperature: 25.0/25/25

weather_data.set_measurements(27, 70, 1012)
# Output:
# Current conditions: 27°C and 70% humidity
# Avg/Max/Min temperature: 26.0/27/25

🌟 Benefits of Using Observer Pattern

  • Loose Coupling: Subjects and observers are loosely coupled - they can interact without detailed knowledge of each other
  • Dynamic Relationships: Observers can be added or removed at runtime
  • Broadcast Communication: One update in the subject can trigger updates in multiple observers
  • Reusability: Both subjects and observers can be reused independently

🔑 OOP Principles Supported

1. Open/Closed Principle

  • The pattern allows adding new observers without modifying the subject
  • New types of observers can be created without changing existing code

2. Single Responsibility Principle

  • The subject focuses on maintaining state and notifying observers
  • Each observer handles its specific response to updates

3. Encapsulation

  • Observers don’t need to know how the subject’s internal state is managed
  • The subject doesn’t need to know what observers do with the data

4. Abstraction

  • The pattern uses interfaces to define the contract between subjects and observers
  • Implementation details are hidden behind these interfaces

🚨 Common Pitfalls

  • Memory Leaks: If observers aren’t properly removed when no longer needed
  • Update Overhead: Too many observers or frequent updates can cause performance issues
  • Unexpected Updates: Cascading updates can occur if observers also act as subjects
  • Consistency Issues: If multiple threads are updating the subject simultaneously

🔄 Real-World Applications

  • Event handling systems in user interfaces
  • Subscription services (newsletters, notifications)
  • Model-View-Controller (MVC) architecture (Model as subject, Views as observers)
  • Distributed systems where components need to stay synchronized