👀 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