Python Observer Design Pattern

1. Definition

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

2. Problem Statement

Suppose you're developing a system where changes in one part of the system need to be broadcasted to other parts of the system without making these parts tightly coupled. How do you ensure that changes in one object (the subject) are automatically reflected in its dependents (observers)?

3. Solution

The Observer Pattern provides a solution by introducing 'subjects' and 'observers'. The subject maintains a list of observers and notifies them of any state changes, typically by calling one of their methods. Observers register themselves with the subject to receive updates.

4. Real-World Use Cases

1. News agencies and subscribers: Where agencies (subject) send updates to their subscribers (observers).

2. Stock market and investors: Where any change in stock prices (subject) gets notified to investors (observers).

3. UI implementations where state changes in one component need to be reflected in other UI components.

5. Implementation Steps

1. Define the Observer and Subject interfaces.

2. Create concrete classes for both Observer and Subject.

3. Implement registration, deregistration, and notification methods in the Subject.

4. Implement update methods in the Observer.

6. Implementation in Python

# Observer interface
class Observer:
    def update(self, message: str) -> None:
        pass
# Concrete Observer
class User(Observer):
    def __init__(self, name: str):
        self.name = name
    def update(self, message: str) -> None:
        print(f"{self.name} received message: '{message}'")
# Subject interface
class Subject:
    def add_observer(self, observer: Observer) -> None:
        pass
    def remove_observer(self, observer: Observer) -> None:
        pass
    def notify_observers(self, message: str) -> None:
        pass
# Concrete Subject
class NotificationService(Subject):
    def __init__(self):
        self._observers = []
    def add_observer(self, observer: Observer) -> None:
        self._observers.append(observer)
    def remove_observer(self, observer: Observer) -> None:
        self._observers.remove(observer)
    def notify_observers(self, message: str) -> None:
        for observer in self._observers:
            observer.update(message)
# Client code
user1 = User("User 1")
user2 = User("User 2")
notification_service = NotificationService()
notification_service.add_observer(user1)
notification_service.add_observer(user2)
notification_service.notify_observers("Observer Pattern implemented!")

Output:

User 1 received message: 'Observer Pattern implemented!'
User 2 received message: 'Observer Pattern implemented!'

Explanation:

1. The Observer and Subject interfaces are defined to ensure the contract for the observers and the subject.

2. Concrete classes User and NotificationService implement these interfaces.

3. The NotificationService (subject) maintains a list of User (observers).

4. When the notify_observers method of NotificationService is called, each registered user gets the update message.

5. This shows a loose coupling between NotificationService and User as they only interact through interfaces.

7. When to use?

Use the Observer Pattern when:

1. Changes to one object might need to be reflected in other objects without knowing how many objects need to be updated.

2. An object should notify other objects without making assumptions about who these objects are.

3. A core part of the system needs to be decoupled from its numerous components, allowing them to evolve independently.


Comments