Observer Design Pattern

observer-pattern

In observer patterns, one of the behavioural design patterns, there are 3 elements in this design pattern: Event, Observer and Subject.

A Subject maintains a list of observers (dependent) so that Subject can notify all the Observers if any change happens via Subject methods.

Consider you have an e-commerce platform and whenever a product is in stock, all subscribers to this product will be sent an email. In this case, Subject will be the Product class and Observer will be the User class. Another example can be news agencies. When you hear the words publisher/subscriber you can imagine the observer pattern.

In Django, views are a type of Observer. Since all of the views are registered via a router, whenever a request is made to Django, the WSGI handler iterates all over the views in the specified router and notifies all of them, but only the necessary view processes the request and returns a response.

One of the software development techniques is Event Driven Development. In distributed systems, Event service is made with the observer pattern. This situation impacted a lot of languages such as C#, which has built-in support for events, and the Game Development sector benefits from this situation heavily.

The general specifics of the Observer pattern are:

  • Defines one-to-one dependency between the Subject and the Observer
  • Encapsulates the core component of the Subject.

This is the most simple Observable pattern example I think:

class Subject:
    def __init__(self):
        self._observers = []

    def register(self, observer):
        self._observers.append(observer)

    def notify_all(self, *args, **kwargs):
        for observer in self._observers:
            observer.notify(self, *args, **kwargs)

    def __str__(self):
        return self.__class__.__name__


class Observer1:
    def __init__(self, subject):
        subject.register(self)

    def notify(self, subject, *args):
        print(type(self).__name__, ':: Got', args, 'From', subject)


class Observer2:
    def __init__(self, subject):
        subject.register(self)

    def notify(self, subject, *args):
        print(type(self).__name__, ':: Got', args, 'From', subject)


subject = Subject()
observer1 = Observer1(subject)
observer2 = Observer2(subject)
observer3 = Observer2(subject)
subject.notify_all("notification")

Output:

Observer1 :: Got ('notification',) From Subject
Observer2 :: Got ('notification',) From Subject
Observer2 :: Got ('notification',) From Subject

Process finished with exit code 0

Different methods of Observer Pattern

There are two different types of observer patterns: Pull and push

The push method

In this method, the main responsible for transforming the event data is the Subject.

  • All of the events are published by the Subject to all registered Observers.
  • What data to be sent from Subject to Observers is defined by the Subject, so Observers may get unnecessary or extra information.

The pull method

In this method, the main responsible for transforming the event data is Observer.

  • Subject sends to all registered Observers when an event occurs.
  • Then the Observers request the data from the Subject
    • Since observers go back to Subject to fetch data, the loose coupling will be affected negatively
  • Observers can request only the required data as opposed to the pushing method

Resources:

Learning Python Design Patterns
Design Patterns in Python