Lesson 2
Introduction to the Observer Pattern
Introduction to the Observer Pattern

Welcome back! We're continuing our exploration of Behavioral Patterns. In this lesson, we will delve into the Observer Pattern, another fundamental pattern that emphasizes object communication and responsibility distribution. This pattern allows an object, known as the subject, to maintain a list of its dependents, called observers, and notify them automatically of any state changes, usually by calling one of their methods.

Previously, we looked at the Command Pattern, which encapsulates a request as an object. Now, let's build on that knowledge and explore how the Observer Pattern facilitates communication between objects in a seamless and efficient manner.

Core Components of the Observer Pattern

In this lesson, you will learn how to implement the Observer Pattern by understanding its main components and their roles. The Observer Pattern typically involves a Subject, Observer, Concrete Observer, and Client.

We'll start by defining the Observer class that lays out the interface for receiving updates.

Python
1from abc import ABC, abstractmethod 2 3class Subscriber(ABC): 4 @abstractmethod 5 def update(self, news): 6 pass

In this code snippet, the Subscriber class defines the update method. This method will be called by the subject to notify subscribers of any updates.

Next, we need to create the NewsPublisher class, which acts as the Subject. This class maintains a list of subscribers and provides methods to add and remove subscribers, as well as to notify them.

Python
1class NewsPublisher: 2 def __init__(self): 3 self.subscribers = [] 4 5 def add_subscriber(self, subscriber): 6 self.subscribers.append(subscriber) 7 8 def remove_subscriber(self, subscriber): 9 self.subscribers.remove(subscriber) 10 11 def publish(self, news): 12 for subscriber in self.subscribers: 13 subscriber.update(news)

The NewsPublisher class has three methods: add_subscriber to add a new subscriber, remove_subscriber to remove an existing subscriber, and publish to notify all subscribers of new news.

Now, let's implement a concrete observer, which in this case will be a specific type of subscriber. This observer will define how to handle the updates received from the subject.

Python
1class ConcreteSubscriber(Subscriber): 2 def __init__(self, name): 3 self.name = name 4 5 def update(self, news): 6 print(f'{self.name} received news: {news}')

The ConcreteSubscriber class extends the Subscriber base class and implements the update method to print the news received.

Finally, let's see how these components work together in the main function, which serves as the client in this pattern.

Python
1if __name__ == "__main__": 2 news_publisher = NewsPublisher() 3 subscriber1 = ConcreteSubscriber("Subscriber 1") 4 subscriber2 = ConcreteSubscriber("Subscriber 2") 5 6 news_publisher.add_subscriber(subscriber1) 7 news_publisher.add_subscriber(subscriber2) 8 9 news_publisher.publish("Breaking News 1") 10 # Output: 11 # Subscriber 1 received news: Breaking News 1 12 # Subscriber 2 received news: Breaking News 1 13 14 news_publisher.remove_subscriber(subscriber1) 15 news_publisher.publish("Breaking News 2") 16 # Output: 17 # Subscriber 2 received news: Breaking News 2

In the main function, we first create an instance of NewsPublisher and two ConcreteSubscriber objects. We then add the subscribers to the publisher, publish some news, remove one subscriber, and publish another piece of news to see how the system reacts to these state changes.

Full Code Example

Here is the complete code put together:

Python
1from abc import ABC, abstractmethod 2 3class Subscriber(ABC): 4 @abstractmethod 5 def update(self, news): 6 pass 7 8class NewsPublisher: 9 def __init__(self): 10 self.subscribers = [] 11 12 def add_subscriber(self, subscriber): 13 self.subscribers.append(subscriber) 14 15 def remove_subscriber(self, subscriber): 16 self.subscribers.remove(subscriber) 17 18 def publish(self, news): 19 for subscriber in self.subscribers: 20 subscriber.update(news) 21 22class ConcreteSubscriber(Subscriber): 23 def __init__(self, name): 24 self.name = name 25 26 def update(self, news): 27 print(f'{self.name} received news: {news}') 28 29if __name__ == "__main__": 30 news_publisher = NewsPublisher() 31 subscriber1 = ConcreteSubscriber("Subscriber 1") 32 subscriber2 = ConcreteSubscriber("Subscriber 2") 33 34 news_publisher.add_subscriber(subscriber1) 35 news_publisher.add_subscriber(subscriber2) 36 37 news_publisher.publish("Breaking News 1") 38 # Output: 39 # Subscriber 1 received news: Breaking News 1 40 # Subscriber 2 received news: Breaking News 1 41 42 news_publisher.remove_subscriber(subscriber1) 43 news_publisher.publish("Breaking News 2") 44 # Output: 45 # Subscriber 2 received news: Breaking News 2
Conclusion

Mastering the Observer Pattern is crucial for designing systems where objects need to maintain synchronized states. This pattern promotes loose coupling, enhances code readability, and improves maintainability.

Consider a news publishing system where multiple subscribers (users) receive updates whenever new articles are published. The Observer Pattern ensures that all subscribers are automatically notified of the new news without the publisher needing to maintain direct dependencies on each subscriber. This design greatly simplifies future system extensions, as new subscriber types can be added without modifying existing code.

The Observer Pattern unlocks the ability to create highly responsive and well-structured software systems. Let's move on to the practice section and see these concepts in action!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.