How the Observer Pattern Works: A Car Analogy

iAmSherif 馃拵 - Nov 13 '23 - - Dev Community

The observer pattern is a behavioral design pattern that establishes a one-to-many relationship between objects, allowing multiple observers to monitor the state of a single object, referred to as the observable. These observers are automatically notified whenever significant events occur in the observable, and the observable maintains a list of its observers, ensuring they stay informed about any changes in its state.

Understanding Observer Pattern with a Car Example

Let's delve into the concept by considering the instrument panel of a car鈥攖he dashboard behind the steering wheel. This panel includes various gauges and lights, providing real-time updates to the driver about the vehicle's status.

Let's explain the definition of Observer pattern:

One-to-Many Relationship

The observer pattern defines a one-to-many relationship, where a single observable object has a connection with multiple observer objects. This relationship is evident in various scenarios:

  • Car and Its Components:

    • Observable: The car itself, capable of changing its state (e.g., speed, fuel level, temperature).
    • Observers: Various components within the car, such as the gear, fuel gauge, and speedometer, each updating itself based on the changes it observes.
  • Company and Staff Emails:

    • Observable: The company, responsible for sending out emails.
    • Observers: Numerous staff members, are automatically notified of any incoming emails.
  • Phone and Its Components:

    • Observable: The phone, with dynamic features like battery level and room brightness.
    • Observers: Components like battery level indicator and ambient light sensor, adjust phone brightness automatically.

Maintaining a List of Observers

In the observer pattern, the object being observed maintains a list of its observers. This list allows seamless communication between the observable and its observers:

  • Car and Its Components:

    • List of Observers: The car keeps track of components like the fuel gauge and speedometer, which observe its state.
  • Company and Staff Emails:

    • List of Observers: The company maintains a list of staff emails.
  • Phone and Its Components:

    • List of Observers: The phone maintains a list of apps or components, like a power button click counter app.

Notifying Observers of State Changes

The crucial aspect of the observer pattern is the automatic notification of observers when the observable's state changes:

  • Company and Staff Emails:

    • Notification: Staff members automatically receive notifications of any new emails from the company.
  • Car and Its Components:

    • Notification: Car components, such as the fuel gauge and speedometer, automatically update themselves in response to car state changes.
  • Phone and Its Components:

    • Notification: The phone adjusts its brightness automatically based on changes in room brightness.

How it works? Let's demonstrate this in code:

# Define the Car class as the observable
class Car:
    def __init__(self):
        # The observable maintains a list of observers which means it has a relationship with these observers
        self._observers: list = []
        self._fuel_level: int = 4 # (in litres)
        self._speed: int = 0

        # An observer can subscribe to the list
    def add(self, observer) -> None:
        self._observers.append(observer)

        # An observer can also unsubscribe from the list
    def remove(self, observer) -> None:
        self._observers.remove(observer)

        # All subscribed observers are notified whenever this method is invoked
    def notify_observers(self) -> None:
        for observer in self._observers:
            observer.update(self)

        # This accelerate method causes a change in the Car's state and it automatically notifies the observers of its changes
    def accelerate(self) -> None:
        self._speed += 1
        notify_observers()

        # This top_up method also causes a change in the Car's state and it automatically notifies the observers of its changes
    def top_up(self, litre) -> None:
        self._fuel_level += litre
        notify_observers()

        # The below methods return the value in fuel_level and speed respectively
    def get_fuel_level(self) -> int:
        return self._fuel_level

    def get_speed(self) -> int:
        return self._speed

Enter fullscreen mode Exit fullscreen mode

IObserver defines a signature for all observers to implement

from abc import ABC, abstractmethod

# Define the Observer interface
class IObserver(abc.ABC):

    @abstractmmethod
    def update(self):
        pass
Enter fullscreen mode Exit fullscreen mode

Let's define concrete observers

# Define concrete observers

class FuelGauge(IObserver):
    def __init__(self, car):
        self._fuel_level = 0
        self.car = car

    def update(self, car) -> None:
        self._fuel_level = car.get_fuel_level()

class Speedometer(IObserver) :
    def __init__(self, car):
        self._speed = 0
        self.car = car

    def update(self, car) -> None:
        self._speed =car.get_speed()

Enter fullscreen mode Exit fullscreen mode

Client class


if __name__ == 'main' : 
    # Create a car object
    car = Car()

    # Create speedometer and fuelGuage objects 
    speedometer: Speedometer = Speedometer(car)
    fuel_guage: FuelGauge = FuelGauge(car)

    # Add the observers to car object
    car.add(speedometer)
    car.add(fuel_gauge)

    # Change the car's state, observers are notified automatically about this change
    car.accelerate()
Enter fullscreen mode Exit fullscreen mode

Conclusion

In summary, the observer pattern facilitates a flexible and decoupled design, allowing objects to communicate without being tightly bound. By exploring the observer pattern through the example of a car, we gain insights into how observables and observers collaborate in a dynamic and efficient manner.

Follow on LinkedIn and Twitter for more on Design patterns:
click to follow on LinkedIn
click to follow on Twitter

. . . . . . . .