Mediator — reducing direct dependencies between objects
Concept
Mediator pattern reduces direct dependencies between objects by introducing a mediator that coordinates their interactions. Objects communicate through the mediator instead of directly with each other.
The problem without Mediator: In a chat room, each user has a reference to every other user. Adding user D means wiring D to A, B, and C. With 10 users: 45 connections. With Mediator (the chat room): each user references only the mediator. The mediator broadcasts. 10 users: 10 connections.
Structure:
- Mediator Interface: Defines communication methods (e.g.,
broadcast(string $from, string $message)). - Concrete Mediator: Knows all colleagues. Routes messages between them.
- Colleague: Has a reference to the Mediator. Sends and receives through it.
Mediator vs Observer: Both decouple. Observer is one-to-many (subject notifies observers). Mediator is many-to-many (any colleague can communicate with any other via the mediator). Mediator encapsulates complex routing logic.
Real-world examples:
- Chat room / message broker.
- Air traffic control (aircraft don't communicate directly — through ATC).
- Event systems (the event dispatcher IS a Mediator).
- MVC controller: mediates between Model and View.
- DOM event system.
Laravel connection: The EventDispatcher / event system is a Mediator. Events are messages. Listeners are colleagues. The dispatcher routes events to listeners without listeners knowing about each other.
Code Example
<?php
// Mediator Interface
interface ChatRoomMediator
{
public function send(string $message, User $sender): void;
public function addUser(User $user): void;
}
// Colleague base
abstract class User
{
public function __construct(
protected readonly string $name,
protected ?ChatRoomMediator $room = null,
) {}
public function joinRoom(ChatRoomMediator $room): void
{
$this->room = $room;
$room->addUser($this);
}
public function send(string $message): void
{
echo "[{$this->name}] sends: {$message}\n";
$this->room?->send($message, $this);
}
abstract public function receive(string $message, string $from): void;
public function getName(): string { return $this->name; }
}
// Concrete Mediator
class ChatRoom implements ChatRoomMediator
{
private array $users = [];
private array $messageLog = [];
public function addUser(User $user): void
{
$this->users[$user->getName()] = $user;
}
public function send(string $message, User $sender): void
{
$this->messageLog[] = ['from' => $sender->getName(), 'message' => $message, 'at' => now()];
foreach ($this->users as $name => $user) {
if ($user !== $sender) {
$user->receive($message, $sender->getName());
}
}
}
public function getHistory(): array { return $this->messageLog; }
}
// Concrete Colleagues
class HumanUser extends User
{
public function receive(string $message, string $from): void
{
echo "[{$this->name}] received from [{$from}]: {$message}\n";
}
}
class BotUser extends User
{
public function receive(string $message, string $from): void
{
echo "[BOT:{$this->name}] auto-reply to [{$from}]\n";
if (str_contains(strtolower($message), 'help')) {
$this->send("Hi {$from}! How can I assist?");
}
}
}
// Usage
$room = new ChatRoom();
$alice = new HumanUser('Alice');
$bob = new HumanUser('Bob');
$bot = new BotUser('HelpBot');
$alice->joinRoom($room);
$bob->joinRoom($room);
$bot->joinRoom($room);
$alice->send("Hello everyone!");
$bob->send("I need help with something");
// BotUser auto-replies to Bob