Skip to content

Observer Pattern - Decouple Publishers from Subscribers

Updated: at 12:00 AM

1. The Problem

Problem: One Change Must Update Many Dependents

Software often has situations where one object changes and many other objects must react to that change. Examples: UI widgets reacting to data changes, event streams, message broadcasting, sensor updates.

Naive approach: manually call each dependent:

observerA.update(data)
observerB.update(data)
observerC.update(data)

Problems:

We need automatic, decoupled, scalable broadcasting of state changes without the publisher depending on concrete observers.

2. The Observer Pattern: Decouple Publishers from Subscribers

Observer pattern decouples who generates data from who reacts to it.

Core idea:

Components:

Benefits:

Use Observer when:

3. Implementation: Observable + Observer in Python

Interfaces

from abc import ABC, abstractmethod

class Observer(ABC):
    @abstractmethod
    def update(self, data: dict[str, str]):
        pass

class Observable(ABC):
    @abstractmethod
    def attach(self, observer: Observer):
        pass

    @abstractmethod
    def detach(self, observer: Observer):
        pass

    @abstractmethod
    def notify(self):
        pass

Display abstraction

class Display(ABC):
    @abstractmethod
    def display(self, data: dict[str, str]):
        pass

class LoggingDisplay(Display):
    def display(self, data: dict[str, str]):
        for key, value in data.items():
            print(key.title(), ": ", value)

Concrete Observers

class SubscriberA(Observer, LoggingDisplay):
    def __init__(self, subject: Observable) -> None:
        self.subject = subject
        self.data = {}
        subject.attach(self)

    def update(self, data: dict[str, str]):
        for key, value in data.items():
            data[key] = f"A: {value}"
        self.data = data
        self.display(data=data)

    def detach(self):
        self.subject.detach(self)


class SubscriberB(Observer, LoggingDisplay):
    def __init__(self, subject: Observable) -> None:
        self.subject = subject
        self.data = {}
        subject.attach(self)

    def update(self, data: dict[str, str]):
        for key, value in data.items():
            data[key] = f"B: {value}"
        self.data = data
        self.display(data=data)

    def detach(self):
        self.subject.detach(self)

Concrete Publisher

class PublisherA(Observable):
    def __init__(self) -> None:
        self.subscribers: list[Observer] = []
        self.value: int = 0

    def attach(self, observer: Observer):
        self.subscribers.append(observer)

    def detach(self, observer: Observer):
        self.subscribers.remove(observer)

    def notify(self):
        for sub in self.subscribers:
            sub.update(data={"value": str(self.value)})
        print("-----")

    def update(self, value):
        self.value = value
        self.notify()

Usage

publisher = PublisherA()
subscribera = SubscriberA(publisher)
subscriberb = SubscriberB(publisher)

publisher.update(10)
publisher.update(20)

subscribera.detach()   # dynamic removal
publisher.update(30)

4. When to Use Observer (and When Not To)

Use Observer when:

Avoid Observer when: