Card 03/ 08

CodeDifficulty: Intermediate3 min

Mediator Template in Python, Java and TypeScript

Three colleagues that never reference each other, and one mediator that reacts to each.

The Mediator template is the pattern in its pure form, with the participant names from the class diagram. Three colleagues, A, B and C, each report a change to the mediator. The mediator's rules are that a change in A updates B and C, a change in B updates C, and a change in C updates nobody. The steps build the Python version, and the tabs give the complete program in three languages, each with its output.

Building the template one part at a time

Mediator

One method that every colleague calls.

py
@abstractmethod
def notify(self, sender: "Colleague", event: str) -> None: ...

Colleague

Reports its own change, and never names another colleague.

py
def change(self, event: str) -> None:
    print(f"{self.name}: {event}")
    self.mediator.notify(self, event)

Concrete mediator

Holds every rule about who reacts to whom.

py
def notify(self, sender: Colleague, event: str) -> None:
    if sender is self.a:
        self.b.react(f"A's {event}")
        self.c.react(f"A's {event}")
    elif sender is self.b:
        self.c.react(f"B's {event}")

The complete template

py
from abc import ABC, abstractmethod


class Mediator(ABC):
    @abstractmethod
    def notify(self, sender: "Colleague", event: str) -> None: ...


class Colleague:
    def __init__(self, name: str, mediator: Mediator) -> None:
        self.name = name
        self.mediator = mediator

    def change(self, event: str) -> None:
        print(f"{self.name}: {event}")
        self.mediator.notify(self, event)

    def react(self, reason: str) -> None:
        print(f"  {self.name} reacts to {reason}")


class ConcreteMediator(Mediator):
    def __init__(self) -> None:
        self.a = Colleague("A", self)
        self.b = Colleague("B", self)
        self.c = Colleague("C", self)

    def notify(self, sender: Colleague, event: str) -> None:
        if sender is self.a:
            self.b.react(f"A's {event}")
            self.c.react(f"A's {event}")
        elif sender is self.b:
            self.c.react(f"B's {event}")


mediator = ConcreteMediator()
mediator.a.change("x")
mediator.b.change("y")
mediator.c.change("z")
OutputRan with python:3.12-slim (Python 3.12.13)
A: x
  B reacts to A's x
  C reacts to A's x
B: y
  C reacts to B's y
C: z
`notify` is the only place that knows who reacts to whom.
java
public class MdTemplate {
    public static void main(String[] args) {
        ConcreteMediator mediator = new ConcreteMediator();
        mediator.a.change("x");
        mediator.b.change("y");
        mediator.c.change("z");
    }
}

interface Mediator {
    void notify(Colleague sender, String event);
}

class Colleague {
    private final String name;
    private final Mediator mediator;

    Colleague(String name, Mediator mediator) {
        this.name = name;
        this.mediator = mediator;
    }

    void change(String event) {
        System.out.println(name + ": " + event);
        mediator.notify(this, event);
    }

    void react(String reason) {
        System.out.println("  " + name + " reacts to " + reason);
    }
}

class ConcreteMediator implements Mediator {
    final Colleague a = new Colleague("A", this);
    final Colleague b = new Colleague("B", this);
    final Colleague c = new Colleague("C", this);

    public void notify(Colleague sender, String event) {
        if (sender == a) {
            b.react("A's " + event);
            c.react("A's " + event);
        } else if (sender == b) {
            c.react("B's " + event);
        }
    }
}
OutputRan with eclipse-temurin:21-jdk (Java 21.0.12)
A: x
  B reacts to A's x
  C reacts to A's x
B: y
  C reacts to B's y
C: z
Each `Colleague` holds a `Mediator`, never another `Colleague`.
ts
interface Mediator {
  notify(sender: Colleague, event: string): void;
}

class Colleague {
  constructor(readonly name: string, private readonly mediator: Mediator) {}

  change(event: string): void {
    console.log(`${this.name}: ${event}`);
    this.mediator.notify(this, event);
  }

  react(reason: string): void {
    console.log(`  ${this.name} reacts to ${reason}`);
  }
}

class ConcreteMediator implements Mediator {
  readonly a = new Colleague("A", this);
  readonly b = new Colleague("B", this);
  readonly c = new Colleague("C", this);

  notify(sender: Colleague, event: string): void {
    if (sender === this.a) {
      this.b.react(`A's ${event}`);
      this.c.react(`A's ${event}`);
    } else if (sender === this.b) {
      this.c.react(`B's ${event}`);
    }
  }
}

const mediator = new ConcreteMediator();
mediator.a.change("x");
mediator.b.change("y");
mediator.c.change("z");
OutputRan with node:22-slim (Node 22.23.1), TypeScript 5.9.3
A: x
  B reacts to A's x
  C reacts to A's x
B: y
  C reacts to B's y
C: z
`sender === this.a` is how the mediator tells the colleagues apart.

A's change reaches B and C, B's reaches C, and C's reaches nobody, as the rules say. Colleague has no field that refers to another colleague, so all three are the same class here and could be used with any mediator.

Every rule about the interaction is in ConcreteMediator.notify. Changing the rules, such as making C's change update A, is an edit to that one method, and no colleague changes. The pattern makes a trade: the colleagues become simple, and the mediator holds what they no longer do.