0

Mediator — reducing direct dependencies between objects

Advanced5 min read·eng-04-008
compare

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
<?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