Introduction
Software Design Patterns help accelerate the development process by providing a reusable blueprint for your code to solve a particular problem. We follow Design Patterns to write generalized, reusable, and readable code that could be easily understood by others familiar with the patterns we've applied.
They encapsulate cumulative experience of software engineers solving the same problems, and represent solutions to common design-related issues.
There are different classifications of design patterns depending on which class of problems they solve - among which the Observer Design Pattern belongs to the Behavioral Pattern class.
This class of patterns determines how objects communicate with each other. In this guide, you will learn everything you need to know about the Observer Design Pattern and understand how we can use it to solve certain problems efficiently.
Observer Design Pattern
The Observer Design Pattern deals with One-to-Many relationships and utilizes events to let subscribed entities know about changes in an observable.
The source of these events is called the subject or observable which sends events as streams. The observers or sinks can subscribe to the observable to obtain the events. The observable keeps track of the list of observers and notifies them of the changes when the state of the observable changes.
This functionality has many implications and implementations, and similar functionality is all around you. It's an extremely simple, yet very effective and wide-spread pattern.
A similar implementation of this design pattern is seen in generating feeds on your social platforms - the Pub/Sub (Publisher/Subscriber) Model/Pattern. When a content publisher publishes their posts, the subscribers get notified of the content. A similar analogy may be people looking out for a flare signal or a firework for a certain event, and reacting (or not) depending on their specific roles.
Does that mean that the Observer Design Pattern and Publish/Subscribe Pattern are the same?
Previously, both patterns were synonymous. Nowadays, each pattern has distinct traits that make them two separate patterns.
The following are the major differences between the Observer Pattern and the Pub/Sub Pattern:
- Observers and Subjects are tightly coupled. The subjects must keep track of their observers. Whereas in the Pub/Sub pattern, they are loosely coupled with a message queue in between observers and subjects.
- The events are passed in a synchronous manner from the Subjects to the Observers. But in Pub/Sub patterns, the events are passed asynchronously.
- In the Observer pattern, both the Subjects and Observers reside on the same application locality whereas they can reside on different localities in the Pub/Sub pattern.
One of the best ways to get a feel for this pattern is to implement it, let's implement it in Python!
Implementation
A basic implementation requires two classes - an Observable
and an Observer
. The Observer
class is initialized with an object as an argument. The object is none other than an Observable
to keep track of, to which it is subscribed upon creation.
The class also has a notify()
function, which triggers a reaction and acknowledges the receipt of a notification/event from the observable:
class Observer:
def __init__(self, observable):
observable.subscribe(self)
def notify(
self,
observable,
*args,
**kwargs
):
print ('Got', args, kwargs, 'From', observable)
The Observable
class is initialized with an empty list to hold the Observer
instances. It also has functions such as subscribe()
to add an observer, notify_observers()
to call the notify()
function on each observer, and unsubscribe()
to remove the observer from the list:
class Observable:
def __init__(self):
self._observers = []
def subscribe(self, observer):
self._observers.append(observer)
def notify_observers(self, *args, **kwargs):
for obs in self._observers:
obs.notify(self, *args, **kwargs)
def unsubscribe(self, observer):
self._observers.remove(observer)
Plugging in all of the above-mentioned components, let's write some code that sets up an observer and observable and sends messages, which triggers a reaction:
# observer_pattern.py
"""
Demonstrating the Observer pattern implementation
"""
# Initializing the subject
subject = Observable()
# Initializing two observers with the subject object
observer1 = Observer(subject)
observer2 = Observer(subject)
# The following message will be notified to 2 observers
subject.notify_observers('This is the 1st broadcast',
kw='From the Observer')
subject.unsubscribe(observer2)
# The following message will be notified to just 1 observer since
# the observer has been unsubscribed
subject.notify_observers('This is the 2nd broadcast',
kw='From the Observer')
Notice that we also unsubscribe an observer before publishing the second message. This will lead to the message being printed only once instead of twice on the second attempt, as it is received by only one subscriber.
Running this code will result in:
$ python observer_pattern.py
Got ('This is the 1st broadcast',) {'kw': 'From the Observer'} From <__main__.Observable object at 0x7f6c50d2fb50>
Got ('This is the 1st broadcast',) {'kw': 'From the Observer'} From <__main__.Observable object at 0x7f6c50d2fb50>
Got ('This is the 2nd broadcast',) {'kw': 'From the Observer'} From <__main__.Observable object at 0x7f6c50d2fb50>
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
As you can see, the observable can directly interact with the observers and vice versa. The observable will be in interaction with the observer as long as the observer is subscribed to the observable's subscription list.
Pros and Cons
With the implementation in place, the pros and cons of this design pattern can be compared as follows:
Pros
-
The one-to-many relationship is defined between the objects. This ensures that when an object is altered, it will lead to a cascade of changes to be applied to the dependent objects.
-
Loosely coupled objects mean that the components can be interchanged.
Cons
-
The communication between the observable and observer is synchronous and with an increased load of subscribing and unsubscribing events, the observable object could be bombarded with requests. This could be mitigated by setting up a sleep time for each request.
-
The sleep solution could also cause a possible loss of speed, performance, and events. This was the main reason for the Pub/Sub pattern to have a message queue in between the publisher and subscriber.
-
Memory leaks are common in this pattern since there is a strong reference between the observer and observable. The observables need to be mandatorily deregistered from the observable object.
To mitigate most of the cons, a message queue was introduced in between the observer and the observable to overcome all these problems, which led to devising the Pub/Sub pattern - a variation of the Observer Pattern.
Conclusion
This guide covers the Observer Pattern, how it can be implemented, and compares its pros and cons.
It's interesting to note that the Observer Pattern is one of the Behavioral Patterns that has led to many of the features we use today, such as RSS feeds, social media feeds, etc.
By being introduced to the nuances of the Design Patterns, it is easier to build the functionality from the ground up. And of course, knowing different Design Patterns allows you to build the best solution for different types of problems.