Mediator
Intent
Section titled “Intent”Mediator introduces an object that encapsulates how a set of objects interact. Instead of each object holding references to all the others, they all talk to the mediator, which decides who needs to know what.
Problem
Section titled “Problem”When several objects need to coordinate, the naive approach is to wire them directly to one another. In a chat room, every participant would keep a list of every other participant. In a form dialog, the submit button would reference the text field, which would reference the checkbox, which would reference the label. The number of connections grows with the square of the number of objects, and each object becomes hard to reuse because it drags its collaborators along.
Mediator collapses that web into a hub and spokes. Each colleague knows only the mediator; when something happens, it tells the mediator, and the mediator routes the consequences to whichever colleagues care. The colleagues become simpler and independently reusable, and the interaction logic lives in one place where you can read and change it. The trade is that the mediator itself can grow large if you let it absorb too much.
Structure
Section titled “Structure”classDiagram
class Mediator {
<<interface>>
+notify(sender, event)
}
class ChatRoom {
-members: List~Colleague~
+register(c Colleague)
+notify(sender, event)
}
class Colleague {
-mediator Mediator
+send(msg)
+receive(from, msg)
}
Mediator <|.. ChatRoom
ChatRoom o-- Colleague : coordinates
Colleague --> Mediator : talks to - Mediator — the interface for the hub. Colleagues call it when something happens.
- ChatRoom — a concrete mediator. It holds the colleagues and contains the logic for routing events between them.
- Colleague — a participant. It knows the mediator but not the other colleagues, so it stays decoupled from them.
Example
Section titled “Example”A chat room as the mediator. Each user sends through the room, and the room broadcasts the message to every other user — no user ever references another user directly.
interface ChatMediator { register(user: User): void; broadcast(from: User, message: string): void;}
class ChatRoom implements ChatMediator { private users: User[] = [];
register(user: User): void { this.users.push(user); }
broadcast(from: User, message: string): void { for (const user of this.users) { if (user !== from) user.receive(from.name, message); } }}
class User { constructor(readonly name: string, private room: ChatMediator) { room.register(this); }
send(message: string): void { this.room.broadcast(this, message); }
receive(from: string, message: string): void { console.log(`[${this.name}] ${from}: ${message}`); }}
const room = new ChatRoom();const alice = new User('Alice', room);const bob = new User('Bob', room);
alice.send('hi'); // [Bob] Alice: hibob.send('hello'); // [Alice] Bob: hellofrom __future__ import annotationsfrom abc import ABC, abstractmethod
class ChatMediator(ABC): @abstractmethod def register(self, user: "User") -> None: ...
@abstractmethod def broadcast(self, sender: "User", message: str) -> None: ...
class ChatRoom(ChatMediator): def __init__(self) -> None: self._users: list[User] = []
def register(self, user: "User") -> None: self._users.append(user)
def broadcast(self, sender: "User", message: str) -> None: for user in self._users: if user is not sender: user.receive(sender.name, message)
class User: def __init__(self, name: str, room: ChatMediator) -> None: self.name = name self._room = room room.register(self)
def send(self, message: str) -> None: self._room.broadcast(self, message)
def receive(self, sender: str, message: str) -> None: print(f"[{self.name}] {sender}: {message}")
room = ChatRoom()alice = User("Alice", room)bob = User("Bob", room)
alice.send("hi") # [Bob] Alice: hibob.send("hello") # [Alice] Bob: hellopackage main
import "fmt"
type Mediator interface { Register(u *User) Broadcast(sender *User, message string)}
type ChatRoom struct { users []*User}
func (r *ChatRoom) Register(u *User) { r.users = append(r.users, u)}
func (r *ChatRoom) Broadcast(sender *User, message string) { for _, u := range r.users { if u != sender { u.Receive(sender.name, message) } }}
type User struct { name string room Mediator}
func NewUser(name string, room Mediator) *User { u := &User{name: name, room: room} room.Register(u) return u}
func (u *User) Send(message string) { u.room.Broadcast(u, message)}
func (u *User) Receive(from, message string) { fmt.Printf("[%s] %s: %s\n", u.name, from, message)}
func main() { room := &ChatRoom{} alice := NewUser("Alice", room) bob := NewUser("Bob", room)
alice.Send("hi") // [Bob] Alice: hi bob.Send("hello") // [Alice] Bob: hello}use std::cell::RefCell;
// The ChatRoom owns the users and routes messages between them.struct ChatRoom { users: Vec<String>, log: RefCell<Vec<String>>,}
impl ChatRoom { fn new() -> Self { ChatRoom { users: Vec::new(), log: RefCell::new(Vec::new()) } }
fn register(&mut self, name: &str) { self.users.push(name.to_string()); }
fn broadcast(&self, sender: &str, message: &str) { for user in &self.users { if user != sender { self.log .borrow_mut() .push(format!("[{user}] {sender}: {message}")); } } }}
fn main() { let mut room = ChatRoom::new(); room.register("Alice"); room.register("Bob");
room.broadcast("Alice", "hi"); room.broadcast("Bob", "hello");
for line in room.log.borrow().iter() { println!("{line}"); // [Bob] Alice: hi / [Alice] Bob: hello }}When to use / trade-offs
Section titled “When to use / trade-offs”- Pro: turns a tangled many-to-many web of references into simple hub-and-spoke connections.
- Pro: colleagues become reusable because they depend only on the mediator interface, not on each other.
- Pro: the interaction logic lives in one readable place rather than scattered across colleagues.
- Con: the mediator can swell into a god object that knows everything, becoming the very complexity you tried to remove.
- Con: it adds a layer of indirection that can obscure who ultimately reacts to an event.
Related patterns
Section titled “Related patterns”- Observer also decouples senders from receivers, but it broadcasts to subscribers rather than centralizing arbitrary routing logic.
- Facade likewise offers one point of contact, but a facade only simplifies one-way calls into a subsystem, whereas a mediator coordinates two-way traffic between peers.