Observer is a behavioral design pattern that lets you define a subscription mechanism to notify multiple objects about any events that happen to the object they’re observing.
Imagine that you have two types of objects: a Customer
and a Store
. The customer is very interested in a particular brand of product (say, it’s a new model of the iPhone) which should become available in the store very soon.
The customer could visit the store every day and check product availability. But while the product is still en route, most of these trips would be pointless.
On the other hand, the store could send tons of emails (which might be considered spam) to all customers each time a new product becomes available. This would save some customers from endless trips to the store. At the same time, it’d upset other customers who aren’t interested in new products.
It looks like we’ve got a conflict. Either the customer wastes time checking product availability or the store wastes resources notifying the wrong customers.
The object that has some interesting state is often called subject, but since it’s also going to notify other objects about the changes to its state, we’ll call it publisher. All other objects that want to track changes to the publisher’s state are called subscribers.
The Observer pattern suggests that you add a subscription mechanism to the publisher class so individual objects can subscribe to or unsubscribe from a stream of events coming from that publisher. Fear not! Everything isn’t as complicated as it sounds. In reality, this mechanism consists of 1) an array field for storing a list of references to subscriber objects and 2) several public methods which allow adding subscribers to and removing them from that list.
Now, whenever an important event happens to the publisher, it goes over its subscribers and calls the specific notification method on their objects.
Real apps might have dozens of different subscriber classes that are interested in tracking events of the same publisher class. You wouldn’t want to couple the publisher to all of those classes. Besides, you might not even know about some of them beforehand if your publisher class is supposed to be used by other people.
That’s why it’s crucial that all subscribers implement the same interface and that the publisher communicates with them only via that interface. This interface should declare the notification method along with a set of parameters that the publisher can use to pass some contextual data along with the notification.
If your app has several different types of publishers and you want to make your subscribers compatible with all of them, you can go even further and make all publishers follow the same interface. This interface would only need to describe a few subscription methods. The interface would allow subscribers to observe publishers’ states without coupling to their concrete classes.
If you subscribe to a newspaper or magazine, you no longer need to go to the store to check if the next issue is available. Instead, the publisher sends new issues directly to your mailbox right after publication or even in advance.
The publisher maintains a list of subscribers and knows which magazines they’re interested in. Subscribers can leave the list at any time when they wish to stop the publisher sending new magazine issues to them.
update
method. The method may have several parameters that let the publisher pass some event details along with the update.In this example, the Observer pattern lets the text editor object notify other service objects about changes in its state.
The list of subscribers is compiled dynamically: objects can start or stop listening to notifications at runtime, depending on the desired behavior of your app.
In this implementation, the editor class doesn’t maintain the subscription list by itself. It delegates this job to the special helper object devoted to just that. You could upgrade that object to serve as a centralized event dispatcher, letting any object act as a publisher.
Adding new subscribers to the program doesn’t require changes to existing publisher classes, as long as they work with all subscribers through the same interface.
// The base publisher class includes subscription management // code and notification methods. class EventManager is private field listeners: hash map of event types and listeners method subscribe(eventType, listener) is listeners.add(eventType, listener) method unsubscribe(eventType, listener) is listeners.remove(eventType, listener) method notify(eventType, data) is foreach (listener in listeners.of(eventType)) do listener.update(data) // The concrete publisher contains real business logic that's // interesting for some subscribers. We could derive this class // from the base publisher, but that isn't always possible in // real life because the concrete publisher might already be a // subclass. In this case, you can patch the subscription logic // in with composition, as we did here. class Editor is public field events: EventManager private field file: File constructor Editor() is events = new EventManager() // Methods of business logic can notify subscribers about // changes. method openFile(path) is this.file = new File(path) events.notify("open", file.name) method saveFile() is file.write() events.notify("save", file.name) // ... // Here's the subscriber interface. If your programming language // supports functional types, you can replace the whole // subscriber hierarchy with a set of functions. interface EventListener is method update(filename) // Concrete subscribers react to updates issued by the publisher // they are attached to. class LoggingListener implements EventListener is private field log: File private field message constructor LoggingListener(log_filename, message) is this.log = new File(log_filename) this.message = message method update(filename) is log.write(replace('%s',filename,message)) class EmailAlertsListener implements EventListener is private field email: string constructor EmailAlertsListener(email, message) is this.email = email this.message = message method update(filename) is system.email(email, replace('%s',filename,message)) // An application can configure publishers and subscribers at // runtime. class Application is method config() is editor = new TextEditor() logger = new LoggingListener( "/path/to/log.txt", "Someone has opened the file: %s"); editor.events.subscribe("open", logger) emailAlerts = new EmailAlertsListener( "admin@example.com", "Someone has changed the file: %s") editor.events.subscribe("save", emailAlerts)
Use the Observer pattern when changes to the state of one object may require changing other objects, and the actual set of objects is unknown beforehand or changes dynamically.
You can often experience this problem when working with classes of the graphical user interface. For example, you created custom button classes, and you want to let the clients hook some custom code to your buttons so that it fires whenever a user presses a button.
The Observer pattern lets any object that implements the subscriber interface subscribe for event notifications in publisher objects. You can add the subscription mechanism to your buttons, letting the clients hook up their custom code via custom subscriber classes.
Use the pattern when some objects in your app must observe others, but only for a limited time or in specific cases.
The subscription list is dynamic, so subscribers can join or leave the list whenever they need to.
update
method.Usage examples: The Observer pattern is pretty common in Python code, especially in the GUI components. It provides a way to react to events happening in other objects without coupling to their classes.
Identification: The pattern can be recognized by subscription methods, that store objects in a list and by calls to the update method issued to objects in that list.
This example illustrates the structure of the Observer design pattern. It focuses on answering these questions:
from __future__ import annotations from abc import ABC, abstractmethod from random import randrange from typing import List class Subject(ABC): """ The Subject interface declares a set of methods for managing subscribers. """ @abstractmethod def attach(self, observer: Observer) -> None: """ Attach an observer to the subject. """ pass @abstractmethod def detach(self, observer: Observer) -> None: """ Detach an observer from the subject. """ pass @abstractmethod def notify(self) -> None: """ Notify all observers about an event. """ pass class ConcreteSubject(Subject): """ The Subject owns some important state and notifies observers when the state changes. """ _state: int = None """ For the sake of simplicity, the Subject's state, essential to all subscribers, is stored in this variable. """ _observers: List[Observer] = [] """ List of subscribers. In real life, the list of subscribers can be stored more comprehensively (categorized by event type, etc.). """ def attach(self, observer: Observer) -> None: print("Subject: Attached an observer.") self._observers.append(observer) def detach(self, observer: Observer) -> None: self._observers.remove(observer) """ The subscription management methods. """ def notify(self) -> None: """ Trigger an update in each subscriber. """ print("Subject: Notifying observers...") for observer in self._observers: observer.update(self) def some_business_logic(self) -> None: """ Usually, the subscription logic is only a fraction of what a Subject can really do. Subjects commonly hold some important business logic, that triggers a notification method whenever something important is about to happen (or after it). """ print("\nSubject: I'm doing something important.") self._state = randrange(0, 10) print(f"Subject: My state has just changed to: {self._state}") self.notify() class Observer(ABC): """ The Observer interface declares the update method, used by subjects. """ @abstractmethod def update(self, subject: Subject) -> None: """ Receive update from subject. """ pass """ Concrete Observers react to the updates issued by the Subject they had been attached to. """ class ConcreteObserverA(Observer): def update(self, subject: Subject) -> None: if subject._state < 3: print("ConcreteObserverA: Reacted to the event") class ConcreteObserverB(Observer): def update(self, subject: Subject) -> None: if subject._state == 0 or subject._state >= 2: print("ConcreteObserverB: Reacted to the event") if __name__ == "__main__": # The client code. subject = ConcreteSubject() observer_a = ConcreteObserverA() subject.attach(observer_a) observer_b = ConcreteObserverB() subject.attach(observer_b) subject.some_business_logic() subject.some_business_logic() subject.detach(observer_a) subject.some_business_logic()
Subject: Attached an observer. Subject: Attached an observer. Subject: I'm doing something important. Subject: My state has just changed to: 0 Subject: Notifying observers... ConcreteObserverA: Reacted to the event ConcreteObserverB: Reacted to the event Subject: I'm doing something important. Subject: My state has just changed to: 5 Subject: Notifying observers... ConcreteObserverB: Reacted to the event Subject: I'm doing something important. Subject: My state has just changed to: 0 Subject: Notifying observers... ConcreteObserverB: Reacted to the event
Observer Design Pattern – raj – Medium
https://medium.com/@rajkumar_p/observer-design-pattern-9786a813cfbb
Design Patterns — A quick guide to Observer pattern.
https://medium.com/datadriveninvestor/design-patterns-a-quick-guide-to-observer-pattern-d0622145d6c2
Design patterns: Observer – Sławomir Kowalski – Medium
https://medium.com/@sawomirkowalski/design-patterns-observer-5832ad7e0ddf
Understanding the Observer Pattern — SitePoint
https://www.sitepoint.com/understanding-the-observer-pattern/
Design Patterns in Python
https://refactoring.guru/design-patterns/python
Design Patterns: Observer in Python
https://refactoring.guru/design-patterns/observer/python/example#lang-features