Accueil / Tutoriels / Mediator

Mediator

Publié le 8 Avril 2025
Conception

Mediator (médiateur en français) est un design pattern comportemental qui a pour objectif de réduire les dépendances entres les objets. Il permet aux objets de communiquer sans se référencer directement les uns avec les autres en passant par un intermédiaire central : le médiateur.

Principe

Le but est que les objets arrêtent de communiquer directement entre eux. A la place, ils communiqueront en utilisant un objet spécial qu’on appelle le médiateur. Le médiateur joue le rôle de manager/orchestrateur qui centralise les échanges. Chaque objet s'adresse uniquement au manager qui se chargera ensuite de faire passer les messages ou de déclencher les bonnes actions. Les objets n’ont plus besoin de se connaître entre eux mais seulement de connaître le manager.

La logique d’interaction entre les objets est centralisée ce qui permet de réduire fortement le couplage entre les objets. Par la suite, il est beaucoup plus facile de modifier ou d’ajouter de nouveaux composants sans casser le code existant.

Exemples d’utilisations

Gestion dynamique d’un formulaire complexe

Imaginons un formulaire composé de nombreux éléments comme un menu déroulant, des sections qui peuvent s’afficher ou se masquer en fonction de choix, des champs obligatoires ou facultatifs et un bouton de validation qui ne s’active que si certains critères sont remplis.

Ici un composant n’a pas à connaître les autres. Cela deviendrait vite très compliqué. Le design pattern Mediator va centraliser toute la logique d’interaction entre les différents composants.

Coordonner des microservices dans une architecture back-end

Sur un site de e-commerce, un utilisateur veut passer une commande ce qui implique entre autres de créer la commande, réserver les stocks, lancer une facturation, notifier le client par mail, déclencher le suivi du colis, etc…

Si chaque service doit appeler directement les autres services, on se retrouve avec un système très fortement couplé et peu évolutif.

En utilisant un médiateur, on peut faire en sorte que chaque service communique avec un orchestrateur qui décidera ensuite quelles actions devront être effectuées.

Points d’attentions

Le design pattern Mediator aussi élégant soit-il n’est pas magique. Il a ses limites et mal utilisé, il peut créer plus de problèmes qu’il n’en résout.

A force de centraliser toute la logique d'interaction entre objets, un médiateur peut devenir un point de congestion et finit par connaître tout le monde et tout gérer.

Le médiateur s’il commence à prendre en charge des règles métiers (et pas juste la communication) devient une “zone grise” entre le client et le domaine (la logique métier). Les responsabilités de chacuns sont difficiles à distinguer et si le code doit évoluer le risque de régressions est important. Le médiateur n’est pas un fourre-tout.

A trop vouloir découpler, le code devient moins lisible. Un objet va appeler appeler le médiateur au lieu d’appeler directement l’objet cible. Pour comprendre les interactions, il faut suivre les différents appels entre objets, ce qui peut dans certains cas rendre le débogage plus complexe.

Implémenter un Mediator

On va créer une interface qui définit un contrat pour la communication entre les composants.

Une classe Mediator qui a toutes les références aux composants va implémenter cette interface et définir la logique de communication.

Chaque objet souhaitant communiquer avec les autres objets va avoir une référence vers le médiateur. Ils ne communiquent jamais directement entre eux. A chaque fois qu’une action a lieu au niveau d’un objet, il en informe le médiateur.

Exemple d’utilisation de Mediator en Go

Prenons le cas d’un système de notification dans un tableau de bord admin. Un utilisateur effectue une action comme ajouter un produit ou supprimer un compte.

Plusieurs modules doivent interagir suite à cette action : le système de log, la gestion des emails, l’historique des évènements, un système de notification, etc…

Le Mediator va orchestrer ces modules sans qu’ils soient couplés les uns aux autres.


package admin

type Mediator interface {
   Notify(sender Component, event string)
}


package admin

type Component interface {
   SetMediator(mediator Mediator)
}


package admin

type NotificationMediator struct {
   Logger    *Logger
   Emailer   *Emailer
   Auditor   *Auditor
   Dashboard *DashboardNotifier
}

func (m *NotificationMediator) Notify(sender Component, event string) {
   switch event {
   case "user_deleted":
      m.Logger.Log("User deleted")
      m.Emailer.Send("admin@example.com", "Un utilisateur a été supprimé.")
      m.Auditor.Record("Suppression utilisateur")
      m.Dashboard.Update("Un utilisateur a été supprimé.")
   }
}


package admin

import "fmt"

type Logger struct{ mediator Mediator }

func (l *Logger) SetMediator(m Mediator) {
   l.mediator = m
}

func (l *Logger) Log(msg string) {
   fmt.Println("[Log]", msg)
}


package admin

import "fmt"

type Emailer struct {
   mediator Mediator
}

func (e *Emailer) SetMediator(m Mediator) {
   e.mediator = m
}

func (e *Emailer) Send(to, msg string) {
   fmt.Println("[Email]", to, msg)
}


package admin

import "fmt"

type Auditor struct {
   mediator Mediator
}

func (a *Auditor) SetMediator(m Mediator) {
   a.mediator = m
}

func (a *Auditor) Record(event string) {
   fmt.Println("[Audit]", event)
}


package admin

import "fmt"

type DashboardNotifier struct {
   mediator Mediator
}

func (d *DashboardNotifier) SetMediator(m Mediator) {
   d.mediator = m
}

func (d *DashboardNotifier) Update(msg string) {
   fmt.Println("[Dashboard]", msg)
}


package main

import "design_patterns/mediator/admin"

func main() {
   logger := &admin.Logger{}
   emailer := &admin.Emailer{}
   auditor := &admin.Auditor{}
   dashboard := &admin.DashboardNotifier{}

   mediator := &admin.NotificationMediator{
      Logger:    logger,
      Emailer:   emailer,
      Auditor:   auditor,
      Dashboard: dashboard,
   }

   logger.SetMediator(mediator)
   emailer.SetMediator(mediator)
   auditor.SetMediator(mediator)
   dashboard.SetMediator(mediator)

   mediator.Notify(nil, "user_deleted")
}

Le même exemple en PHP


namespace Practice\DesignPatterns\Mediator;

interface MediatorInterface {
   public function notify(object $sender, string $event): void;
}


namespace Practice\DesignPatterns\Mediator;

interface ComponentInterface
{
   public function setMediator(MediatorInterface $mediator): void;
}


namespace Practice\DesignPatterns\Mediator;

class NotificationMediator implements MediatorInterface
{
   private Logger            $logger;
   private Emailer           $emailer;
   private Auditor           $auditor;
   private DashboardNotifier $dashboard;

   public function __construct(Logger $logger, Emailer $emailer, Auditor $auditor, DashboardNotifier $dashboard)
   {
       $this->logger    = $logger;
       $this->emailer   = $emailer;
       $this->auditor   = $auditor;
       $this->dashboard = $dashboard;
   }

   public function notify($sender, string $event): void
   {
       if ($event === 'user_deleted') {
           $this->logger->log("User deleted");
           $this->emailer->send("admin@example.com", "Un utilisateur a été supprimé.");
           $this->auditor->record("Suppression utilisateur");
           $this->dashboard->update("Un utilisateur a été supprimé.");
       }
   }
}


namespace Practice\DesignPatterns\Mediator;

class Logger implements ComponentInterface
{
   private MediatorInterface $mediator;

   public function setMediator(MediatorInterface $mediator): void
   {
       $this->mediator = $mediator;
   }

   public function log(string $msg): void
   {
       echo "[Log] $msg\n";
   }
}


namespace Practice\DesignPatterns\Mediator;

class Emailer implements ComponentInterface
{
   private MediatorInterface $mediator;

   public function setMediator(MediatorInterface $mediator): void
   {
       $this->mediator = $mediator;
   }

   public function send(string $to, string $msg): void
   {
       echo "[Email] $to: $msg\n";
   }
}


namespace Practice\DesignPatterns\Mediator;

class Auditor implements ComponentInterface
{
   private MediatorInterface $mediator;

   public function setMediator(MediatorInterface $mediator): void
   {
       $this->mediator = $mediator;
   }

   public function record(string $event): void
   {
       echo "[Audit] $event\n";
   }
}


namespace Practice\DesignPatterns\Mediator;

class DashboardNotifier implements ComponentInterface
{
   private MediatorInterface $mediator;

   public function setMediator(MediatorInterface $mediator): void
   {
       $this->mediator = $mediator;
   }

   public function update(string $msg): void
   {
       echo "[Dashboard] $msg\n";
   }
}


declare(strict_types=1);

use Practice\DesignPatterns\Mediator\Auditor;
use Practice\DesignPatterns\Mediator\DashboardNotifier;
use Practice\DesignPatterns\Mediator\Emailer;
use Practice\DesignPatterns\Mediator\Logger;
use Practice\DesignPatterns\Mediator\NotificationMediator;

$logger    = new Logger();
$emailer   = new Emailer();
$auditor   = new Auditor();
$dashboard = new DashboardNotifier();

$mediator = new NotificationMediator($logger, $emailer, $auditor, $dashboard);

$logger->setMediator($mediator);
$emailer->setMediator($mediator);
$auditor->setMediator($mediator);
$dashboard->setMediator($mediator);

$mediator->notify(null, 'user_deleted');

Conclusion

J'espère que grâce à cet article vous êtes désormais plus à l'aise avec le design pattern Mediator. Vous pouvez retrouver mon article sur les patrons de conception ou les autres tutoriels sur Visitor, Prototype, Decorator, Strategy et Observer.