Observer Design Pattern (Behavioral)
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.
Problem
Section titled βProblemβImagine you have two types of objects: a Customer and a Store. The customer is very interested in a particular brand of device (e.g., a new iPhone) which should become available in the store very soon.
The customer could visit the store every day to 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 thousands 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, but it would annoy others who have no interest in the new products.
Solution
Section titled βSolutionβThe object that has some interesting state is often called the subject, but since itβs also going to notify other objects about changes to its state, weβll call it the 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. This mechanism consists of:
- An array field for storing a list of references to subscriber objects.
- 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-World Analogy
Section titled βReal-World Analogyβ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. You can stop being a subscriber at any time when you want the publisher to stop sending new magazine issues to you.
Structure
Section titled βStructureβ- Publisher issues events of interest to other objects. These events occur when the publisher changes its state or executes some behaviors. Publishers contain a subscription infrastructure that lets new subscribers join and current subscribers leave the list.
- Subscriber Interface declares the notification interface. In most cases, it consists of a single
updatemethod. - Concrete Subscribers perform some actions in response to notifications issued by the publisher. All of these classes must implement the same interface so the publisher isnβt coupled to concrete classes.
- Client creates publisher and subscriber objects separately and then registers subscribers for publisher updates.
Structure (Class Diagram)
Section titled βStructure (Class Diagram)βclassDiagram
class Publisher {
-subscribers: Subscriber[]
+subscribe(s: Subscriber)
+unsubscribe(s: Subscriber)
+notifySubscribers()
+mainBusinessLogic()
}
class Subscriber {
<<interface>>
+update(context)
}
class ConcreteSubscriberA {
+update(context)
}
class ConcreteSubscriberB {
+update(context)
}
Publisher o-- Subscriber
Subscriber <|-- ConcreteSubscriberA
Subscriber <|-- ConcreteSubscriberB
Client ..> Publisher
Client ..> ConcreteSubscriberA
Pseudocode
Section titled βPseudocodeβ# The publisher interface includes subscription management# methods and a notification method.class EventManager: private field listeners: hash map of lists of listeners
method subscribe(eventType, listener): listeners.add(eventType, listener)
method unsubscribe(eventType, listener): listeners.remove(eventType, listener)
method notify(eventType, data): foreach (listener in listeners.of(eventType)): listener.update(data)
# The concrete publisher contains real business logic that's# interesting for some subscribers.class Editor: public field events: EventManager private field file: File
constructor Editor(): events = new EventManager()
method openFile(path): this.file = new File(path) events.notify("open", file.name)
# The subscriber interface declares the update method.interface EventListener: method update(filename)Applicability
Section titled βApplicabilityβ- One-to-Many Dependencies: 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.
- Temporary Monitoring: Use the pattern when some objects in your app must observe others, but only for a limited time or in specific cases.
How to Implement
Section titled βHow to Implementβ- Look at your business logic and try to break it down into two parts: the core functionality, independent of other code, will act as the publisher; the rest will turn into a set of subscriber classes.
- Declare the subscriber interface. At a minimum, it should declare a single
updatemethod. - Declare the publisher interface and describe the methods for adding a subscriber object to and removing it from the list.
- Decide where to put the actual subscription list and the implementation of subscription methods (usually a base class is best).
- Create concrete subscriber classes. Implement the
updatemethod. - The client must create all necessary subscribers and register them with the right publishers.
Pros and Cons
Section titled βPros and ConsβPros:
- Open/Closed Principle: You can introduce new subscriber classes without having to change the publisherβs code (and vice-versa if thereβs a publisher interface).
- You can establish relations between objects at runtime.
Cons:
- Subscribers are notified in random order.
Relations with other Design Patterns
Section titled βRelations with other Design Patternsβ- Chain of Responsibility, Command, Mediator, and Observer address various ways of connecting senders and receivers of requests.
- The difference between Mediator and Observer is that Mediator centralizes communication between a set of objects, while Observer distributes it by letting objects subscribe to events.
Code Examples
Section titled βCode ExamplesβCode Examples (C#)
Section titled βCode Examples (C#)β// Observerpublic interface IObserver{ void Update(float temperature);}
// Subjectpublic class WeatherStation{ private readonly List<IObserver> _observers = new(); private float _temperature;
public void AddObserver(IObserver observer) => _observers.Add(observer); public void RemoveObserver(IObserver observer) => _observers.Remove(observer);
public void SetTemperature(float temp) { _temperature = temp; NotifyObservers(); }
private void NotifyObservers() { foreach (var obs in _observers) obs.Update(_temperature); }}
// Concrete Observerpublic class Display : IObserver{ public void Update(float temperature) { Console.WriteLine($"Temperatura atual: {temperature}Β°C"); }}
// Usageclass Program{ static void Main() { var station = new WeatherStation(); var display = new Display();
station.AddObserver(display); station.SetTemperature(22.5f); station.SetTemperature(25.0f); }}Code Examples (Python)
Section titled βCode Examples (Python)βfrom __future__ import annotationsfrom abc import ABC, abstractmethodfrom random import randrangefrom typing import List
class Subject(ABC): """ The Subject interface declares a set of methods for managing subscribers. """ @abstractmethod def attach(self, observer: Observer) -> None: pass
@abstractmethod def detach(self, observer: Observer) -> None: pass
@abstractmethod def notify(self) -> None: pass
class ConcreteSubject(Subject): """ The Subject owns some important state and notifies observers when the state changes. """ _state: int = None _observers: List[Observer] = []
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)
def notify(self) -> None: print("Subject: Notifying observers...") for observer in self._observers: observer.update(self)
def some_business_logic(self) -> None: 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: pass
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__": subject = ConcreteSubject()
obs1 = ConcreteObserverA() subject.attach(obs1)
obs2 = ConcreteObserverB() subject.attach(obs2)
subject.some_business_logic() subject.some_business_logic()
subject.detach(obs2) subject.some_business_logic()Code Examples (C++)
Section titled βCode Examples (C++)β#include <iostream>#include <vector>#include <algorithm>
// Observerclass IObserver {public: virtual ~IObserver() = default; virtual void Update(float temperature) = 0;};
// Subjectclass WeatherStation {private: std::vector<IObserver*> observers; float temperature{};
public: void AddObserver(IObserver* obs) { observers.push_back(obs); }
void RemoveObserver(IObserver* obs) { observers.erase( std::remove(observers.begin(), observers.end(), obs), observers.end() ); }
void SetTemperature(float temp) { temperature = temp; NotifyObservers(); }
private: void NotifyObservers() { for (auto* obs : observers) { obs->Update(temperature); } }};
// Concrete Observerclass Display : public IObserver {public: void Update(float temperature) override { std::cout << "Temperatura atual: " << temperature << "Β°C" << std::endl; }};
// Usageint main() { WeatherStation station; Display display;
station.AddObserver(&display);
station.SetTemperature(22.5f); station.SetTemperature(25.0f);
return 0;}Code Examples (Java)
Section titled βCode Examples (Java)β// Observerpublic interface IObserver { void update(float temperature);}
// Subjectimport java.util.ArrayList;import java.util.List;
public class WeatherStation {
private final List<IObserver> observers = new ArrayList<>(); private float temperature;
public void addObserver(IObserver observer) { observers.add(observer); }
public void removeObserver(IObserver observer) { observers.remove(observer); }
public void setTemperature(float temp) { this.temperature = temp; notifyObservers(); }
private void notifyObservers() { for (IObserver obs : observers) { obs.update(temperature); } }}
// Concrete Observerpublic class Display implements IObserver {
@Override public void update(float temperature) { System.out.println("Temperatura atual: " + temperature + "Β°C"); }}
// Usagepublic class Program { public static void main(String[] args) { WeatherStation station = new WeatherStation(); Display display = new Display();
station.addObserver(display); station.setTemperature(22.5f); station.setTemperature(25.0f); }}Code Examples (JavaScript)
Section titled βCode Examples (JavaScript)β// Observer (simulated interface)class IObserver { update(temperature) { throw new Error("update() must be implemented"); }}
// Subjectclass WeatherStation { constructor() { this.observers = []; this.temperature = 0; }
addObserver(observer) { this.observers.push(observer); }
removeObserver(observer) { this.observers = this.observers.filter(o => o !== observer); }
setTemperature(temp) { this.temperature = temp; this.notifyObservers(); }
notifyObservers() { for (const obs of this.observers) { obs.update(this.temperature); } }}
// Concrete Observerclass Display extends IObserver { update(temperature) { console.log(`Temperatura atual: ${temperature}Β°C`); }}
// Usageconst station = new WeatherStation();const display = new Display();
station.addObserver(display);station.setTemperature(22.5);station.setTemperature(25.0);Code Examples (Visual Basic)
Section titled βCode Examples (Visual Basic)β' ============================' Observer' ============================Public Interface IObserver Sub Update(temperature As Single)End Interface
' ============================' Subject' ============================Public Class WeatherStation Private ReadOnly _observers As New List(Of IObserver)() Private _temperature As Single
Public Sub AddObserver(observer As IObserver) _observers.Add(observer) End Sub
Public Sub RemoveObserver(observer As IObserver) _observers.Remove(observer) End Sub
Public Sub SetTemperature(temp As Single) _temperature = temp NotifyObservers() End Sub
Private Sub NotifyObservers() For Each obs In _observers obs.Update(_temperature) Next End SubEnd Class
' ============================' Concrete Observer' ============================Public Class Display Implements IObserver
Public Sub Update(temperature As Single) Implements IObserver.Update Console.WriteLine($"Temperatura atual: {temperature}Β°C") End SubEnd Class
' ============================' Usage' ============================Module Program Sub Main() Dim station As New WeatherStation() Dim display As New Display()
station.AddObserver(display) station.SetTemperature(22.5F) station.SetTemperature(25.0F) End SubEnd ModuleCode Examples (Rust)
Section titled βCode Examples (Rust)β// Observertrait Observer { fn update(&self, temperature: f32);}
// Subjectstruct WeatherStation { observers: Vec<Box<dyn Observer>>, temperature: f32,}
impl WeatherStation { fn new() -> Self { Self { observers: Vec::new(), temperature: 0.0, } }
fn add_observer(&mut self, observer: Box<dyn Observer>) { self.observers.push(observer); }
fn remove_observer(&mut self, index: usize) { self.observers.remove(index); }
fn set_temperature(&mut self, temp: f32) { self.temperature = temp; self.notify_observers(); }
fn notify_observers(&self) { for obs in &self.observers { obs.update(self.temperature); } }}
// Concrete Observerstruct Display;
impl Observer for Display { fn update(&self, temperature: f32) { println!("Temperatura atual: {}Β°C", temperature); }}
// Usagefn main() { let mut station = WeatherStation::new(); station.add_observer(Box::new(Display));
station.set_temperature(22.5); station.set_temperature(25.0);}Code Examples (PHP)
Section titled βCode Examples (PHP)β<?php
// Observerinterface IObserver{ public function update(float $temperature): void;}
// Subjectclass WeatherStation{ /** @var IObserver[] */ private array $observers = []; private float $temperature;
public function addObserver(IObserver $observer): void { $this->observers[] = $observer; }
public function removeObserver(IObserver $observer): void { $this->observers = array_filter( $this->observers, fn($obs) => $obs !== $observer ); }
public function setTemperature(float $temp): void { $this->temperature = $temp; $this->notifyObservers(); }
private function notifyObservers(): void { foreach ($this->observers as $obs) { $obs->update($this->temperature); } }}
// Concrete Observerclass Display implements IObserver{ public function update(float $temperature): void { echo "Temperatura atual: {$temperature}Β°C\n"; }}
// Usage$station = new WeatherStation();$display = new Display();
$station->addObserver($display);$station->setTemperature(22.5);$station->setTemperature(25.0);Code Examples (Go)
Section titled βCode Examples (Go)βpackage main
import ( "fmt")
// Observertype Observer interface { Update(temperature float32)}
// Subjecttype WeatherStation struct { observers []Observer temperature float32}
func NewWeatherStation() *WeatherStation { return &WeatherStation{ observers: []Observer{}, }}
func (ws *WeatherStation) AddObserver(o Observer) { ws.observers = append(ws.observers, o)}
func (ws *WeatherStation) RemoveObserver(o Observer) { for i, obs := range ws.observers { if obs == o { ws.observers = append(ws.observers[:i], ws.observers[i+1:]...) break } }}
func (ws *WeatherStation) SetTemperature(temp float32) { ws.temperature = temp ws.notifyObservers()}
func (ws *WeatherStation) notifyObservers() { for _, obs := range ws.observers { obs.Update(ws.temperature) }}
// Concrete Observertype Display struct{}
func (Display) Update(temperature float32) { fmt.Printf("Temperatura atual: %.1fΒ°C\n", temperature)}
// Usagefunc main() { station := NewWeatherStation() display := Display{}
station.AddObserver(display) station.SetTemperature(22.5) station.SetTemperature(25.0)}Code Examples (Ruby)
Section titled βCode Examples (Ruby)β# Observerclass Observerdef update(temperature) raise NotImplementedError, "Subclasses must implement update"endend
# Subjectclass WeatherStationdef initialize @observers = [] @temperature = nilend
def add_observer(observer) @observers << observerend
def remove_observer(observer) @observers.delete(observer)end
def set_temperature(temp) @temperature = temp notify_observersend
private
def notify_observers @observers.each { |obs| obs.update(@temperature) }endend
# Concrete Observerclass Display < Observerdef update(temperature) puts "Temperatura atual: #{temperature}Β°C"endend
# Usagestation = WeatherStation.newdisplay = Display.new
station.add_observer(display)station.set_temperature(22.5)station.set_temperature(25.0)Code Examples (Perl)
Section titled βCode Examples (Perl)β#!/usr/bin/perluse strict;use warnings;
############################# Observer (Interface-like)############################package Observer;
sub update { die "update() must be implemented by subclass";}
############################# Subject############################package WeatherStation;
sub new { my ($class) = @_; return bless { observers => [], temperature => undef }, $class;}
sub add_observer { my ($self, $observer) = @_; push @{ $self->{observers} }, $observer;}
sub remove_observer { my ($self, $observer) = @_; @{ $self->{observers} } = grep { $_ ne $observer } @{ $self->{observers} };}
sub set_temperature { my ($self, $temp) = @_; $self->{temperature} = $temp; $self->_notify_observers();}
sub _notify_observers { my ($self) = @_; foreach my $obs (@{ $self->{observers} }) { $obs->update($self->{temperature}); }}
############################# Concrete Observer############################package Display;our @ISA = ('Observer');
sub new { bless {}, shift }
sub update { my ($self, $temperature) = @_; print "Temperatura atual: $temperatureΒ°C\n";}
############################# Usage############################package main;
my $station = WeatherStation->new();my $display = Display->new();
$station->add_observer($display);
$station->set_temperature(22.5);$station->set_temperature(25.0);Code Examples (TypeScript)
Section titled βCode Examples (TypeScript)β// Observerinterface IObserver { update(temperature: number): void;}
// Subjectclass WeatherStation { private observers: IObserver[] = []; private temperature: number = 0;
addObserver(observer: IObserver): void { this.observers.push(observer); }
removeObserver(observer: IObserver): void { this.observers = this.observers.filter(o => o !== observer); }
setTemperature(temp: number): void { this.temperature = temp; this.notifyObservers(); }
private notifyObservers(): void { for (const obs of this.observers) { obs.update(this.temperature); } }}
// Concrete Observerclass Display implements IObserver { update(temperature: number): void { console.log(`Temperatura atual: ${temperature}Β°C`); }}
// Usageconst station = new WeatherStation();const display = new Display();
station.addObserver(display);station.setTemperature(22.5);station.setTemperature(25.0);