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
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
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()
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()
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