Observer

The observer pattern is used to notify a list of objects about a state change of the observed component. We have already discussed this pattern briefly in the previous chapter, but here we will discuss some practical examples of a situation where this pattern could be applied.

For instance, let's imagine we have an application that stores marketing materials (briefs, presentations, videos, and flyers) and legal documents in digital form for the sales department of a large company. The company is large, with many sales representatives, and has multiple documents to maintain. A system performs multiple tasks to process these digital documents and make sure that sales representatives are always aware which materials have been updated and that documents can be easily shared with their prospects:

Therefore, almost every component of the system is concerned about events related to every document lifetime. We could design our application in such a way that every component receives information about modifications that are done to documents. The observer pattern is especially good in this situation because it's an observer's responsibility to decide which types of events are interesting for it. From there, every independent component will get notified every time there's an event that it has subscribed to. Of course, this requires that all the code that deals with the actual state of an observed object (for example, creating, modifying, or deleting documents) is triggering such events. But this is way easier than maintaining a manually long list of hooks to call on every time something happens to the observed object. A popular web framework that supports this programming pattern is Django, with its mechanism of signals.

An Event class can be implemented for registration of observers in Python by working at the class level:

class Event: 
    _observers = [] 
 
    def __init__(self, subject): 
        self.subject = subject 
 
    @classmethod 
    def register(cls, observer): 
        if observer not in cls._observers: 
            cls._observers.append(observer) 
 
    @classmethod 
    def unregister(cls, observer): 
        if observer in cls._observers: 
            cls._observers.remove(observer) 
 
    @classmethod 
    def notify(cls, subject): 
        event = cls(subject) 
        for observer in cls._observers: 
            observer(event)

The idea is that observers register themselves using the Event class method and get notified with Event instances that carry the subject that triggered them. Here is an example of the concrete Event subclass with some observers subscribed to its notifications:

class WriteEvent(Event): 
    def __repr__(self): 
        return 'WriteEvent' 
 
 
def log(event): 
    print( 
        '{!r} was fired with subject "{}"' 
        ''.format(event, event.subject) 
    ) 
 
 
class AnotherObserver(object): 
    def __call__(self, event): 
        print( 
            "{!r} trigerred {}'s action" 
            "".format(event, self.__class__.__name__) 
        ) 
 
 
WriteEvent.register(log) 
WriteEvent.register(AnotherObserver()) 

And here is an example result of firing the event with the WriteEvent.notify() method:

>>> WriteEvent.notify("something happened")
WriteEvent was fired with subject "something happened" WriteEvent trigerred AnotherObserver's action

This implementation is simple and serves only an illustrational purpose. To make it fully functional, it could be enhanced by the following:

De-coupling your code is fun and the observer is the right pattern to do it. It modularizes your application and makes it more extensible. If you want to use an existing tool, try Blinker. It provides fast and simple object-to-object and broadcast signaling for Python objects.