CHAPITRE 15 : FACADE
Le schéma de câblage ressemblait à un plat de spaghettis explosé. Des dizaines de composants : amplificateur, lecteur Blu-ray, tuner, projecteur, écran motorisé, système audio multi-canaux, décodeur streaming. Allumer le home cinéma ? Une procédure de 15 étapes dans le bon ordre. Éteindre ? Une autre séquence complexe. Les clients se plaignaient. Ils ne voulaient pas connaître les détails. Ils voulaient juste "Regarder un film". Il fallait un bouton unique. Une interface simple qui cache le chaos. Il fallait une Façade.
Simplification d’un sous-système
Le pattern Facade (ou Façade) fournit une interface simplifiée et unifiée vers un ensemble d'interfaces d'un sous-système complexe. Il ne change pas la fonctionnalité du sous-système ; il la rend simplement plus facile à utiliser.
La Façade est une classe qui connaît intimement les rouages du sous-système. Elle orchestre les appels entre les différentes classes, gère les séquences, les dépendances et les erreurs, et expose juste quelques méthodes simples au client.
Protection contre la complexité
La Façade protège le client de trois choses :
- La complexité : Le client n'a pas besoin de connaître les 10 classes et leurs interactions.
- L'instabilité : Si l'implémentation interne du sous-système change (on remplace le lecteur Blu-ray par un lecteur 4K), seule la Façade doit être mise à jour. Le client reste inchangé.
- Les dépendances : Le client ne dépend plus de chaque classe du sous-système, mais uniquement de la Façade. Cela réduit radicalement le couplage.
Exemple : Le système Home Cinéma
// --- LE SOUS-SYSTÈME COMPLEXE (Spaghetti code) ---
class Amplificateur {
private volume: number = 20;
private isOn: boolean = false;
allumer() { this.isOn = true; console.log("Amplificateur allumé."); }
eteindre() { this.isOn = false; console.log("Amplificateur éteint."); }
setVolume(niveau: number) { this.volume = niveau; console.log(`Volume réglé sur ${niveau}.`); }
setSonSurround() { console.log("Son Surround activé (5.1)."); }
setSonStéréo() { console.log("Son Stéréo activé."); }
}
class LecteurBluRay {
private isOn: boolean = false;
private film?: string;
allumer() { this.isOn = true; console.log("Lecteur Blu-ray allumé."); }
eteindre() { this.isOn = false; console.log("Lecteur Blu-ray éteint."); }
play(film: string) { this.film = film; console.log(`Lecture du film: "${film}".`); }
stop() { this.film = undefined; console.log("Arrêt de la lecture."); }
}
class Projecteur {
private isOn: boolean = false;
private mode?: string;
allumer() { this.isOn = true; console.log("Projecteur allumé."); }
eteindre() { this.isOn = false; console.log("Projecteur éteint."); }
setModeWideScreen() { this.mode = 'wide'; console.log("Mode écran large activé."); }
setModeCinema() { this.mode = 'cinema'; console.log("Mode cinéma activé."); }
}
class EcranMotorise {
private isDown: boolean = true;
descendre() { this.isDown = true; console.log("Écran descendu."); }
monter() { this.isDown = false; console.log("Écran monté."); }
}
class LumieresAmbiance {
private intensite: number = 100;
attenuer(niveau: number) { this.intensite = niveau; console.log(`Lumières atténuées à ${niveau}%.`); }
allumerPlein() { this.intensite = 100; console.log("Lumières au maximum."); }
}
// --- LA FAÇADE (Le bouton magique) ---
class HomeCinemaFacade {
// La Façade COMPOSE toutes les parties du sous-système.
constructor(
private ampli: Amplificateur,
private lecteur: LecteurBluRay,
private projecteur: Projecteur,
private ecran: EcranMotorise,
private lumieres: LumieresAmbiance
) {}
// Méthode simple pour le client.
regarderFilm(titreFilm: string): void {
console.log("\n🎬 Préparez-vous pour le film...");
// La Façade orchestre la séquence complexe.
this.lumieres.attenuer(10);
this.ecran.descendre();
this.projecteur.allumer();
this.projecteur.setModeCinema();
this.ampli.allumer();
this.ampli.setSonSurround();
this.ampli.setVolume(25);
this.lecteur.allumer();
this.lecteur.play(titreFilm);
console.log("🎬 Profitez du film !\n");
}
arreterFilm(): void {
console.log("\n🛑 Arrêt du film en cours...");
// Séquence d'arrêt, dans l'ordre inverse logique.
this.lecteur.stop();
this.lecteur.eteindre();
this.ampli.eteindre();
this.projecteur.eteindre();
this.ecran.monter();
this.lumieres.allumerPlein();
console.log("🛑 Système éteint.\n");
}
// Autres scénarios simplifiés
ecouterMusique(): void {
console.log("\n🎵 Mode musique activé...");
this.lumieres.attenuer(30);
this.ecran.monter(); // On n'a pas besoin de l'écran.
this.projecteur.eteindre();
this.ampli.allumer();
this.ampli.setSonStéréo();
this.ampli.setVolume(30);
console.log("🎵 Prêt pour la musique.\n");
}
}
// --- CLIENT (Heureux et ignorant) ---
function client() {
// 1. Configuration initiale (câblage) - fait une fois.
const ampli = new Amplificateur();
const lecteur = new LecteurBluRay();
const projecteur = new Projecteur();
const ecran = new EcranMotorise();
const lumieres = new LumieresAmbiance();
// 2. On donne tout ça à la Façade.
const homeCinema = new HomeCinemaFacade(ampli, lecteur, projecteur, ecran, lumieres);
// 3. Le client utilise des méthodes SIMPLES.
homeCinema.regarderFilm("Le Parrain");
// Après le film...
// homeCinema.arreterFilm();
// homeCinema.ecouterMusique();
}
client();
// Affiche une séquence orchestrée et propre, sans que le client
// n'ait à connaître les détails.
Façade vs Service (ou Manager)
C'est une confusion fréquente. Une Façade n'est pas un "Service" ou un "Manager" qui encapsule une seule responsabilité ou un seul type d'objet.
- Façade : Orchestrateur hétérogène. Elle coordonne plusieurs classes différentes d'un même sous-système pour accomplir une tâche de haut niveau (ex: "regarder un film", "passer une commande"). Elle est tournée vers l'extérieur (le client) pour simplifier l'accès.
- Service/Manager : Encapsulateur homogène. Il regroupe des opérations liées à un même type d'entité ou une même responsabilité technique (ex:
UserServicepour les opérations CRUD sur les utilisateurs,EmailServicepour l'envoi d'emails). Il est tourné vers l'intérieur de l'application, pour organiser le code.
Façade vs Adapter
Ils enveloppent tous deux d'autres objets, mais avec des intentions opposées :
- Adapter : Change l'interface d'un objet pour le rendre compatible avec un client qui attend une autre interface.
- Façade : Fournit une nouvelle interface simple pour tout un sous-système de plusieurs objets. Elle ne cherche pas à rendre compatible, elle cherche à simplifier.
Avantages et inconvénients
Avantages :
- Réduit fortement le couplage entre le client et un sous-système complexe.
- Rend le sous-système plus facile à utiliser et à apprendre.
- Isole le client des changements dans le sous-système.
- Peut améliorer la lisibilité et la maintenabilité du code client.
Inconvénients :
- Peut devenir une classe "dieu" si on lui fait trop faire. Il faut limiter son périmètre à un sous-système cohérent.
- Cache des fonctionnalités : Si le client a besoin d'un contrôle fin sur une partie du sous-système, il devra contourner la Façade (ce qui peut être accepté, la Façade n'empêche pas l'accès direct, elle le facilite juste).
J'imaginai la télécommande universelle avec un gros bouton "FILM". La Façade, c'était ça. Une abstraction pratique, qui transformait une procédure complexe en une action unique.
C'était un pattern humble, souvent sous-estimé. Il ne créait pas de nouvelles fonctionnalités, il les organisait. Il était le visage présentable d'une machinerie interne qui, elle, pouvait rester complexe et spécialisée.
Le prochain pattern structurel serait l'avant dernier : Proxy. Un gardien, un intermédiaire, une entité qui se place entre le client et l'objet réel pour contrôler l'accès. Une autre forme d'enveloppe, mais avec des intentions de contrôle, pas de simplification.