Observer design pattern in Python
The observer pattern is a useful design principle allowing some objects to be notified about a change that happens in another object. It provides a way to implement a "subscription mechanism", widely used in web, GUI, or elsewhere.
Here is a short example of when it can be useful.
Imagine you have a class that holds data about the current price of a stock. The price might change over time, and you want to get a notification on your phone every time the price changes. But not only you, but also your wife wants to get a notification on her phone, and some of your friends as well.
In that scenario, the class holding price is the publisher (more generally an Observable) and your phone is the reader or subscriber (more generally an Observer). A publisher might have multiple readers.
The point is, that the Publisher is aware of all these Readers, and every time there is a change in price, it sends a notification to all of them. The readers do not need to do anything extra, they do not need to actively ask every now and then if there was a change. They simply register and wait for the message.
In order to make it work this way, the Publisher has to implement three methods: one for adding a reader, one for removing a reader, and one for notifying all the readers.
Here is how it can be done.
class I_Observable:
""" Abstract class serving as an Interface for Observable class. """
def add_observer(self, I_Observer):
pass
def remove_observer(self, I_Observer):
pass
def notify_observers(self):
pass
class Publisher(I_Observable):
""" Concrete implementation of I_Observable interface. """
observers = []
price = 0
def add_observer(self, I_Observer):
self.observers.append(I_Observer)
def remove_observer(self, I_Observer):
self.observers.remove(I_Observer)
def notify_observers(self):
for i in self.observers:
i.update(self.price)
Here I have two classes, the one above serves as an abstract class or interface. Python does not really have an "interface" in a way as Java for example, but it can be achieved this way. Interface do not implement the methods. It has only signatures.
A concrete class Publisher inherits for that abstract interface and implements the body of the methods. It overrides them. The implementation of this class is quite straightforward, all it does is it holds a list of the readers, the add method adds a particular reader, the remove method removes it. Notice that in the parameter the methods take an I_Observer which is an abstract class representing a reader. More on that in a second.
The last method to be implemented is notify. It simply loops over the list of readers and calls update() on each of them.
Now, what is the update() method and what is the I_Observer?
This update method is the only method, that each reader has to implement.
Here is how it looks like
class I_Observer:
""" Abstract class serving as an interface for Observer class. """
def update(self):
pass
class Reader(I_Observer):
""" Concrete implementation of Observer. """
price = 0
def update(self, price: int):
self.price = price
Again, I have an abstract class/interface called I_Observer. A concrete class Reader inherits from it. It has one member function that updates the price.
At this point, both Publisher and Reader implement all the methods needed to communicate with each other. The publisher can add, remove and notify all readers, and Reader simply updates its state whenever it notified.
And that is all there is to it. That is all the observer pattern.
Let's test it
In order to test it, let's add one more method to the Publisher which will set the price - this is just for the simulation purpose, we need the possibility to manually set and change the price. In rea-life scenario, this change would be happening somehow automatically..
Here is how the class looks like now.
class Publisher(I_Observable):
""" Concrete implementation of I_Observable interface. """
observers = []
price = 0
def add_observer(self, I_Observer):
self.observers.append(I_Observer)
def remove_observer(self, I_Observer):
self.observers.remove(I_Observer)
def notify_observers(self):
for i in self.observers:
i.update(self.price)
# this is the only change
def set_price(self, price: int):
self.price = price
Also, to see the change, we add a display method to the Reader, and add the name of the reader. Remember, one Publisher can have MANY readers.
class Reader(I_Observer):
""" Concrete implementation of Observer. """
price = 0
def __init__(self, device_name):
self.device_name = device_name
def update(self, price: int):
self.price = price
self.display() # show the results as soon as it updates
# this method is added to see the results
def display(self):
print(f"Current price shown on {self.device_name} is {self.price} USD")
Now time to test it.
if __name__ == "__main__":
prague_ws = Publisher()
lukas_phone = Reader("Lukas's iPhone")
ayses_phone = Reader("Ayse's iPhone")
prague_ws.add_observer(lukas_phone)
prague_ws.add_observer(ayses_phone)
prague_ws.set_price(10)
prague_ws.notify_observers()
prague_ws.set_price(13)
# add other two readers
fr = Reader("Frank's iPhone")
mi = Reader("Mikes's CellPhone")
prague_ws.add_observer(fr)
prague_ws.add_observer(mi)
prague_ws.notify_observers()
# remove one
prague_ws.remove_observer(ayses_phone)
prague_ws.notify_observers()
# change
prague_ws.set_price(233)
prague_ws.remove_observer(mi)
prague_ws.notify_observers()
Prints.
Current price shown on Lukas's iPhone is 10 USD
Current price shown on Ayse's iPhone is 10 USD
Current price shown on Lukas's iPhone is 13 USD
Current price shown on Ayse's iPhone is 13 USD
Current price shown on Frank's iPhone is 13 USD
Current price shown on Mikes's CellPhone is 13 USD
Current price shown on Lukas's iPhone is 13 USD
Current price shown on Frank's iPhone is 13 USD
Current price shown on Mikes's CellPhone is 13 USD
Current price shown on Lukas's iPhone is 233 USD
Current price shown on Frank's iPhone is 233 USD