Le plan de la salle des opérations montrait un désordre de fils et de talkies-walkies. Chaque équipe – filature, écoute, analyse – communiquait directement avec les autres. Un brouhaha constant, des ordres qui se croisent, des dépendances enchevêtrées. Le chaos. Ils ont installé un central radio. Une seule voix pour coordonner toutes les équipes. Désormais, les équipes ne se parlent plus directement. Elles parlent au central. Le central décide qui doit savoir quoi. C’est le Médiateur.
Centralisation des échanges
Le pattern Mediator définit un objet qui encapsule la manière dont un ensemble d’objets interagissent. Il promeut le couplage faible en empêchant les objets de se référer explicitement les uns aux autres. Au lieu de cela, ils ne communiquent qu’avec le médiateur.
Le problème résolu est celui d’un réseau de connexions N-à-N entre objets (collegues). Si chaque objet doit connaître plusieurs autres, le système devient rigide, difficile à modifier et à comprendre. Le médiateur devient le point unique de coordination.
Réduction du couplage
Sans médiateur, chaque « collègue » dépend directement des autres. Avec un médiateur, ils ne dépendent que de l’interface du médiateur. Cela simplifie la maintenance : on peut changer la façon dont les collègues interagissent en modifiant uniquement le médiateur, sans toucher aux collègues eux-mêmes.
Exemple : Système de contrôle aérien
Imaginons des avions et des tours de contrôle. Sans médiateur, chaque avion devrait connaître tous les autres avions dans son espace aérien pour éviter les collisions. C’est impossible et dangereux. Le médiateur (la tour de contrôle) centralise l’information et donne les instructions.
// Les 'Colleagues' - Les objets qui interagissent, mais ne se connaissent pas directement.
interface Avion {
nom: string;
recevoirMessage(de: string, message: string): void;
atterrir(): void;
decoller(): void;
}
// Le Mediator - L'interface de coordination
interface TourDeControle {
enregistrerAvion(avion: Avion): void;
envoyerMessage(de: string, vers: string, message: string): void;
demanderAutorisationAtterrissage(avion: Avion): void;
notifierDecollage(avion: Avion): void;
}
// Implémentation concrète du Médiateur
class TourDeControleO_Hare implements TourDeControle {
private avions: Map<string, Avion> = new Map(); // Registre des avions
private pisteOccupee: boolean = false;
private fileAttente: Avion[] = [];
enregistrerAvion(avion: Avion): void {
this.avions.set(avion.nom, avion);
console.log(`[TOUR] ${avion.nom} enregistré.`);
}
envoyerMessage(de: string, vers: string, message: string): void {
const destinataire = this.avions.get(vers);
if (destinataire) {
console.log(`[TOUR] Transmission de ${de} à ${vers} : "${message}"`);
destinataire.recevoirMessage(de, message);
} else {
console.log(`[TOUR] Destinataire ${vers} inconnu.`);
}
}
demanderAutorisationAtterrissage(avion: Avion): void {
console.log(`[TOUR] Demande d'atterrissage de ${avion.nom}. Piste occupée ? ${this.pisteOccupee}`);
if (!this.pisteOccupee) {
this.pisteOccupee = true;
console.log(`[TOUR] ✅ ${avion.nom}, autorisation accordée. Vous pouvez atterrir.`);
avion.recevoirMessage('Tour', 'Autorisation atterrissage accordée.');
} else {
console.log(`[TOUR] ⏳ ${avion.nom}, piste occupée. Mise en file d'attente.`);
this.fileAttente.push(avion);
avion.recevoirMessage('Tour', 'En attente, piste occupée.');
}
}
notifierDecollage(avion: Avion): void {
console.log(`[TOUR] ${avion.nom} a décollé. Libération de la piste.`);
this.pisteOccupee = false;
// Vérifier la file d'attente
const suivant = this.fileAttente.shift();
if (suivant) {
console.log(`[TOUR] Notification à ${suivant.nom} : la piste est libre.`);
this.demanderAutorisationAtterrissage(suivant);
}
}
}
// Implémentation concrète d'un Colleague
class AirbusA320 implements Avion {
constructor(public nom: string, private mediateur: TourDeControle) {
this.mediateur.enregistrerAvion(this);
}
recevoirMessage(de: string, message: string): void {
console.log(` [${this.nom}] Message de ${de} : "${message}"`);
}
atterrir(): void {
console.log(`\n[${this.nom}] Demande d'autorisation d'atterrissage.`);
this.mediateur.demanderAutorisationAtterrissage(this);
}
decoller(): void {
console.log(`\n[${this.nom}] Décollage en cours...`);
// Simulation d'une action avant de notifier
setTimeout(() => {
console.log(`[${this.nom}] A décollé.`);
this.mediateur.notifierDecollage(this);
}, 1000);
}
contacter(autreAvion: string, message: string): void {
console.log(`\n[${this.nom}] Demande de contact avec ${autreAvion} via la tour.`);
this.mediateur.envoyerMessage(this.nom, autreAvion, message);
}
}
// --- Scénario ---
console.log("=== SYSTÈME DE CONTRÔLE AÉRIEN (Mediator Pattern) ===\n");
// Création du médiateur (la tour de contrôle)
const tour = new TourDeControleO_Hare();
// Création des avions (colleagues). Ils ne se connaissent pas, seul le médiateur les connaît tous.
const vol1 = new AirbusA320('Vol AF123', tour);
const vol2 = new AirbusA320('Vol UA456', tour);
const vol3 = new AirbusA320('Vol DL789', tour);
// Communication via le médiateur
vol1.contacter('Vol UA456', "On se suit à 5km de distance, tout va bien ?");
// Gestion des ressources (piste) via le médiateur
console.log("\n--- Gestion de la piste ---");
vol1.atterrir(); // AF123 atterrit (piste libre)
vol2.atterrir(); // UA456 mis en attente (piste occupée)
vol1.decoller(); // AF123 décolle, libère la piste, UA456 peut atterrir
vol3.atterrir(); // DL789 mis en attente derrière...
Médiateur devenu “god object”
C’est le risque majeur. Si on n’y prend pas garde, le médiateur peut devenir une classe Dieu (God Object) : une classe monolithique qui connaît trop de détails sur trop de choses, concentrant une logique métier énorme et devenant un goulot d’étranglement et un point de défaillance unique.
Symptômes :
- Le médiateur a des milliers de lignes de code.
- Il contient de la logique métier qui devrait être dans les collègues.
- Chaque nouveau type d’interaction nécessite de modifier le médiateur.
- Le médiateur devient difficile à tester.
Comment l’éviter :
- Responsabilité limitée : Le médiateur ne doit faire que de la coordination. Il ne doit pas contenir de logique métier propre aux collègues. Son rôle est de router les messages, de gérer les ressources partagées, de faire respecter des protocoles.
- Médiateurs multiples : Pour un système complexe, on peut avoir plusieurs médiateurs hiérarchiques ou spécialisés (un pour la communication, un pour la gestion des ressources).
- Utiliser des Events/Commands : Au lieu que le médiateur appelle directement des méthodes sur les collègues, il peut émettre des événements ou envoyer des commandes. Les collègues s’abonnent aux événements qui les concernent. Cela découple encore plus.
- Interface fine : L’interface du médiateur ne doit pas être une fourre-tout. Elle peut être composée de plusieurs interfaces plus petites (ex:
CommunicationMediator,ResourceMediator).
Quand l’utiliser ?
- Quand un ensemble d’objets communiquent de manière complexe et que ces communications sont désordonnées.
- Quand on veut réutiliser un objet dans un contexte différent sans qu’il ait à connaître tous ses nouveaux « voisins ».
- Quand on veut centraliser le contrôle sur un ensemble d’interactions (comme dans les contrôleurs de dialogues UI, les jeux, les systèmes de chat).
Différence avec Observer
Ils sont souvent confondus, mais leur intention est opposée :
- Observer : Découplage un-à-plusieurs. Un sujet notifie passivement des observateurs qui se sont abonnés. Le sujet ne sait pas ce que les observateurs font.
- Mediator : Découplage plusieurs-à-plusieurs. Les collègues envoient activement des messages ou des requêtes au médiateur, qui orchestre la réponse. Le médiateur a un rôle actif de contrôle et de décision.
La salle des opérations était maintenant calme. Seul le central parlait. Le Mediator avait remplacé le chaos par un ordre centralisé. C’était une solution puissante pour les systèmes où les interactions sont nombreuses et complexes.
Mais il fallait veiller à ne pas créer un monstre, un « god object » qui saurait tout et ferait tout, devenant la nouvelle source de complexité.
Le prochain pattern, State, aborderait un problème comportemental interne : comment gérer proprement un objet dont le comportement change radicalement avec son état interne, sans se perdre dans une forêt de conditions.