Card 03/ 09

CodeDifficulty: Intermediate3 min

Observer Template in Python, Java and TypeScript

Attach two observers, change the subject, and watch both react.

The Observer template is the pattern in its pure form, with the participant names from the class diagram. Two observers attach to a subject, the state changes and both react, then one detaches and only the other hears the second change. The steps add each participant in Python, and the tabs give the complete program in three languages, each with its output.

Building the template one participant at a time

Observer

Declares the one method the subject calls.

py
class Observer(ABC):
    @abstractmethod
    def update(self, subject: "Subject") -> None: ...

Subject

Keeps a list, and calls every observer on it.

py
def attach(self, observer: Observer) -> None:
    self._observers.append(observer)

def notify(self) -> None:
    for observer in self._observers:
        observer.update(self)

The change

Setting the state is what triggers the notification.

py
@state.setter
def state(self, value: int) -> None:
    self._state = value
    self.notify()

Concrete observers

React by reading the state they were told about.

py
class ConcreteObserverA(Observer):
    def update(self, subject: Subject) -> None:
        print(f"A saw {subject.state}")

The complete template

py
from abc import ABC, abstractmethod


class Observer(ABC):
    @abstractmethod
    def update(self, subject: "Subject") -> None: ...


class Subject:
    def __init__(self) -> None:
        self._observers: list[Observer] = []
        self._state = 0

    def attach(self, observer: Observer) -> None:
        self._observers.append(observer)

    def detach(self, observer: Observer) -> None:
        self._observers.remove(observer)

    def notify(self) -> None:
        for observer in self._observers:
            observer.update(self)

    @property
    def state(self) -> int:
        return self._state

    @state.setter
    def state(self, value: int) -> None:
        self._state = value
        self.notify()


class ConcreteObserverA(Observer):
    def update(self, subject: Subject) -> None:
        print(f"A saw {subject.state}")


class ConcreteObserverB(Observer):
    def update(self, subject: Subject) -> None:
        print(f"B saw {subject.state}")


subject = Subject()
a, b = ConcreteObserverA(), ConcreteObserverB()
subject.attach(a)
subject.attach(b)
subject.state = 1
subject.detach(b)
subject.state = 2
OutputRan with python:3.12-slim (Python 3.12.13)
A saw 1
B saw 1
A saw 2
The setter calls `notify`, so assigning `subject.state` reaches every observer.
java
import java.util.ArrayList;
import java.util.List;

public class ObTemplate {
    public static void main(String[] args) {
        Subject subject = new Subject();
        Observer a = new ConcreteObserverA();
        Observer b = new ConcreteObserverB();
        subject.attach(a);
        subject.attach(b);
        subject.setState(1);
        subject.detach(b);
        subject.setState(2);
    }
}

interface Observer {
    void update(Subject subject);
}

class Subject {
    private final List<Observer> observers = new ArrayList<>();
    private int state;

    void attach(Observer observer) {
        observers.add(observer);
    }

    void detach(Observer observer) {
        observers.remove(observer);
    }

    void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(this);
        }
    }

    int getState() {
        return state;
    }

    void setState(int state) {
        this.state = state;
        notifyObservers();
    }
}

class ConcreteObserverA implements Observer {
    public void update(Subject subject) {
        System.out.println("A saw " + subject.getState());
    }
}

class ConcreteObserverB implements Observer {
    public void update(Subject subject) {
        System.out.println("B saw " + subject.getState());
    }
}
OutputRan with eclipse-temurin:21-jdk (Java 21.0.12)
A saw 1
B saw 1
A saw 2
`notifyObservers` avoids clashing with `Object.notify`, which every Java class already has.
ts
interface Observer {
  update(subject: Subject): void;
}

class Subject {
  private readonly observers: Observer[] = [];
  private value = 0;

  attach(observer: Observer): void {
    this.observers.push(observer);
  }

  detach(observer: Observer): void {
    this.observers.splice(this.observers.indexOf(observer), 1);
  }

  notify(): void {
    for (const observer of this.observers) {
      observer.update(this);
    }
  }

  get state(): number {
    return this.value;
  }

  set state(value: number) {
    this.value = value;
    this.notify();
  }
}

class ConcreteObserverA implements Observer {
  update(subject: Subject): void {
    console.log(`A saw ${subject.state}`);
  }
}

class ConcreteObserverB implements Observer {
  update(subject: Subject): void {
    console.log(`B saw ${subject.state}`);
  }
}

const subject = new Subject();
const a = new ConcreteObserverA();
const b = new ConcreteObserverB();
subject.attach(a);
subject.attach(b);
subject.state = 1;
subject.detach(b);
subject.state = 2;
OutputRan with node:22-slim (Node 22.23.1), TypeScript 5.9.3
A saw 1
B saw 1
A saw 2
A `set state` accessor makes the assignment trigger `notify`.

The first change reaches both observers, in the order they attached. After detach(b) the second change reaches only A, so B saw 2 never appears. The subject's code names neither observer class: it holds a list typed by the interface, and a third observer would attach without any edit to Subject.