The Observer pattern, is a design pattern from Object Oriented Programming. This pattern is intended to make things easier for us.
Why observer pattern?
Let's assume we have a platform that allows us to distribute weather data. Here, it would make sense to connect a database behind it, which would then be accessed by all end devices. But what if we can only work on a serial level? Then it makes sense to include each end device in the code. The disadvantage here is the administration effort and the compactness of the code. This is where the Observer pattern comes into play.
The Observer pattern, as the name suggests, is based on the principle of observation. This is a behavior pattern. It is used to pass on changes to one object to other objects.
We looked at the UML of the pattern. Here we see the main classes, Subject and Observer. These are interfaces that provide the basic methods for implentation. In the Subject class, which represents the object to be observed, three methods are defined.
The method register, which registers a new client with the subject, a method to remove or deregister said client. With the method updateAll, all clients are notified of a change, so that the clients are kept up to date.
The Observer class, has only the update method defined, which retrieves the data from a subject.
Our concrete subjects are the implementation of the Subject interface. All methods are implemented here. Furthermore, a list of clients(Observer) is also created here. The method getStatus, returns the data of our concrete subject. A concrete subject can be anything that creates or passes data. In our example it is a weather station.
Example use-case observer pattern
A concrete Observer can be e.g. a thermometer or a display etc.... This class implements from the interface Observer, the method update. This class has as private attribute our subject which will be queried later during an update.
Implement the pattern
The implementation of this pattern is very simple. I have created a small example in Python.
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import abc
class Subject(object):
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def register(self, obs):
raise Exception("Not implemented")
@abc.abstractmethod
def unregister(self, obs):
raise Exception("Not implemented")
@abc.abstractmethod
def updateAll(self):
raise Exception("Not implemented")
Looking at the Subject class, we see the three prototype definitions of the methods. The register and unregister methods both have a pass parameter that registers/unregisters our newly instantiated clients.
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import abc
class Observer(object):
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def update(self):
raise Exception("Not implemented")
The class Observer, has only the defined method update, which later queries the concrete subject. Let's move on to our weather station.
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from Subject import Subject
class WeatherStation(Subject):
__devices = None
temp = 10.0
def __init__(self):
self.__devices = []
def register(self, obs):
if not obs in self.__devices:
self.__devices.append(obs)
def unregister(self, obs):
if obs in self.__devices:
self.__devices.remove(obs)
def updateAll(self):
for dev in self.__devices:
dev.update()
def getStatus(self):
return self.temp
As a small example, here is the weather station implemented with fixed data. The class WeatherStation inherits from the class Subject. All defined methods from the interface are implemented here. The list, which later contains all clients, is declared as a private attribute. The temperature temp, is declared as a public attribute with assigned value. The method getStatus, simply returns the current temperature. In the constructor, all attributes that have not yet been initialized are initialized. With this, our weather station is ready as a concrete subject.
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import abc
class Thermometer(object):
__subject = None
def __init__(self, subject):
self.__subject = subject
def update(self):
temp = self.__subject.getStatus()
print("Thermometer:", temp)
If we look at the Thermometer class, we see that it inherits from the Observer class. The private attribute subject, will later store our weather station to fetch the temperature from it later. The constructor assigns the subject to the private attribute. The other observers, I will not discuss further, because they have the same structure. I'll just attach the code.
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import abc
class Display(object):
__subject = None
def __init__(self, subject):
self.__subject = subject
def update(self):
temp = self.__subject.getStatus()
print("Display:", temp)
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import abc
class Laptop(object):
__subject = None
def __init__(self, subject):
self.__subject = subject
def update(self):
temp = self.__subject.getStatus()
print("Laptop:", temp)
In our main program, we intance our weather station and simply create a few observers. In the loop, we can now change the temperature in the weather station via an input.
Finish the pattern
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from WeatherStation import WeatherStation
from Laptop import Laptop
from Thermometer import Thermometer
if __name__ == "__main__":
w = WeatherStation()
l = Laptop(w)
t = Thermometer(w)
w.register(l)
w.register(t)
while True:
w.temp = input("Enter temp:")
w.updateAll()
If we want to run the whole thing now, we see that all registered clients output the same temperature.
Enter temp:10.5
('Laptop:', 10.5)
('Thermometer:', 10.5)
Enter temp:-10
('Laptop:', -10)
('Thermometer:', -10)
Enter temp:39.5
('Laptop:', 39.5)
('Thermometer:', 39.5)
Enter temp:
I hope I could bring someone closer to the Observer pattern 😃