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:
- Video materials are converted to files of different sizes and re-encoded with portable audio-video codecs.
- PDF documents have generated previews that are used as thumbnails in the company's CMS system.
- All new documents are collected in a weekly newsletter broadcast to all sales department employees.
- New confidential materials are encrypted to provide additional data safety.
- Users that have downloaded specific materials previously are notified about updates.
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:
- Allowing the developer to change the order or events
- Making the event object hold more information than just the subject
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.