Observer Pattern

Issue

  • One or more objects needs to know. when a different objects (Subject) changes its state

  • When all these objects need to ask the Subject object individually for potential changes (e.g. every 10 ms), there will be a lot of queries (Polling) -> lots of queries

Solution

  • The Subject pushes change information to its Observers (subscribers) objects

  • all Observer objects need to register for a Subject object to receive changes

  • when a change occurs in Subject the information is pushed to all registered Oberserver objects

Note

Subject class will be referred to as Observable from here on for clearer naming.

Definition

The Observer Pattern defines a one-to-many dependency between object so that when one object changes state, all of its dependencies are notified and updated automatically.

        classDiagram
    Observer "0..*" <-- Observable : HAS-A
    Observer <|-- ConcreteObserver : implements
    Observable <|-- ConcreteObservable : implements
    ConcreteObservable <-- ConcreteObserver : HAS-A
    class Observable {
        <<interface>>
        +add(Observer)
        +remove(Observer)
        +notify()
    }
    class Observer {
        <<interface>>
        +update()
    }
    class ConcreteObservable {
        +add(Observer)
        +remove(Observer)
        +notify()
        +get_state()
    }
    class ConcreteObserver {
        +update()
    }
    
  • a ConcreteObservable updates its registered ConcreteObservers by calling their update() method

  • a ConcreteObservable class has additional methods to do create or obtain the data, it is supposed to push towards its registered ConcreteObservers

Warning

The ConcreteObservable class violates the Open-Closed Principle since it handles the notification of its registered ConcreteObservers and has other logic of whatever it is the object is doing (e.g. pulling some data from a weather station), here bound to the method get_state().

  • a ConcreteObservable passes itself into the constructor of ConcreteObserver. This allows the ConcreteObservable to call the update() method without passing itself or the updated data, since the ConcreteObserver can reference it itself

Example

Find a template example for Python at https://refactoring.guru/design-patterns/observer/python/example.

A weather station, which contains a sensor to measure the temperature (and other data) and multiple devices, which each display the data.

        classDiagram
    Observer "0..*" <-- Observable : HAS-A
    Observer <|-- PhoneDisplay : implements
    Observer <|-- DeskDisplay : implements
    Observable <|-- WeatherStation : implements
    WeatherStation <-- PhoneDisplay : HAS-A
    WeatherStation <-- DeskDisplay : HAS-A
    class Observable {
        <<interface>>
        +add(Observer)
        +remove(Observer)
        +notify()
    }
    class Observer {
        <<interface>>
        +update()
    }
    class WeatherStation {
        +add(Observer)
        +remove(Observer)
        +notify()
    }
    class PhoneDisplay {
        +update()
    }
    class DeskDisplay {
        +update()
    }
    
from abc import ABC, abstractmethod


class Observer(ABC):

    @abstractmethod
    def update(self):
        pass


class Observable(ABC):

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

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

    @abstractmethod
    def notify(self):
        pass


class WeatherStation(Observable):

    observers = []
    temperature = 20

    def add(self, observer: Observer):
        self.observers.append(observer)

    def remove(self, observer: Observer):
        self.observers.remove(observer)

    def notify(self):
        for observer in self.observers:
            observer.update()

    def get_temperature(self):
        return self.temperature

    # add method to retrieve temperature from sensor


class PhoneDisplay(Observer):

    temperature = None

    def __init__(self, weather_station: WeatherStation):
        self.station = weather_station

    def update(self):
        self.temperature = self.station.get_temperature()

    def display(self):
        print(f"Phone Display: {self.temperature}")

    # some additional method(s) on how to display temperature


class DeskDisplay(Observer):

    temperature = None

    def __init__(self, weather_station: WeatherStation):
        self.station = weather_station

    def update(self):
        self.temperature = self.station.get_temperature()

    def display(self):
        print(f"Desk Display: {self.temperature}")

    # some additional method(s) on how to display temperature


if __name__ == "__main__":

    station = WeatherStation()
    phone_display = PhoneDisplay(station)
    desk_display = DeskDisplay(station)

    station.add(phone_display)
    station.add(desk_display)

    # initial display
    phone_display.update()
    desk_display.update()
    phone_display.display()
    desk_display.display()

    # mocking a temperature change
    station.temperature = 25
    station.notify()

    # updated display
    phone_display.display()
    desk_display.display()