Le coffre-fort était là, massif et froid. Mais on n’y touchait pas directement. Il y avait un gardien. Il vérifiait les identités, tenait un journal des accès, ouvrait seulement à certaines heures. Le coffre-fort, c’était l’objet réel. Le gardien, c’était le Proxy. Un intermédiaire qui contrôle l’accès. Pas pour simplifier (comme la Façade), mais pour réguler, protéger, optimiser.
Principe général
Le pattern Proxy fournit un substitut ou un représentant d’un autre objet. Il implémente la même interface que l’objet réel, ce qui permet au client de l’utiliser de manière transparente. Mais derrière, le Proxy contrôle l’accès à l’objet réel, ajoutant un niveau d’indirection. Cette indirection sert à différentes choses : chargement paresseux, contrôle d’accès, journalisation, optimisation réseau.
Proxy virtuel (Lazy Loading)
Le plus courant. Il retarde la création ou le chargement d’un objet coûteux jusqu’au moment où il est réellement nécessaire.
// Interface commune
interface Image {
afficher(): void;
}
// Objet réel, LOURD à charger
class ImageReelle implements Image {
constructor(private nomFichier: string) {
this.chargerDepuisDisque(); // Opération coûteuse immédiatement
}
private chargerDepuisDisque(): void {
console.log(`⏳ Chargement LOURD de l'image: ${this.nomFichier}...`);
// Simulation d'un chargement long
}
afficher(): void {
console.log(`✅ Affichage de l'image: ${this.nomFichier}`);
}
}
// Proxy Virtuel : reporte le chargement
class ProxyImage implements Image {
private imageReelle: ImageReelle | null = null;
constructor(private nomFichier: string) {
console.log(`Proxy créé pour ${nomFichier} (chargement différé).`);
}
afficher(): void {
// Création de l'objet réel UNIQUEMENT au premier appel
if (this.imageReelle === null) {
this.imageReelle = new ImageReelle(this.nomFichier);
}
// Délégation à l'objet réel
this.imageReelle.afficher();
}
}
// Client
function afficherGalerie() {
const images: Image[] = [
new ProxyImage("vacances_1.jpg"), // Création instantanée du proxy
new ProxyImage("vacances_2.jpg"),
new ProxyImage("vacances_3.jpg")
];
console.log("\n--- Galerie configurée (aucune image chargée) ---\n");
// Seule la première image sera réellement chargée
images[0].afficher(); // ➡️ Ici, le chargement lourd se produit
images[0].afficher(); // ➡️ Deuxième appel : objet déjà chargé, immédiat
// images[1].afficher(); // Ne serait chargée que si on décommente
}
afficherGalerie();
Proxy de protection (Sécurité)
Il contrôle l’accès à l’objet réel en fonction des droits du client.
interface DocumentSensible {
lire(): string;
modifier(contenu: string): void;
}
class DocumentReel implements DocumentSensible {
constructor(private contenu: string) {}
lire(): string {
return this.contenu;
}
modifier(nouveauContenu: string): void {
this.contenu = nouveauContenu;
console.log("Document modifié.");
}
}
class ProxyProtecteur implements DocumentSensible {
constructor(private document: DocumentReel, private utilisateur: { role: string }) {}
lire(): string {
// Tout le monde peut lire
console.log(`[Proxy] Lecture autorisée pour ${this.utilisateur.role}`);
return this.document.lire();
}
modifier(nouveauContenu: string): void {
// Seuls les admins peuvent modifier
if (this.utilisateur.role !== 'admin') {
console.log(`❌ [Proxy] Accès REFUSÉ. Modification réservée aux admins.`);
return;
}
console.log(`[Proxy] Modification autorisée pour admin.`);
this.document.modifier(nouveauContenu);
}
}
// Utilisation
const docSecret = new DocumentReel("Plan de la mission.");
const utilisateurLambda = { role: 'user' };
const administrateur = { role: 'admin' };
const proxyPourUser = new ProxyProtecteur(docSecret, utilisateurLambda);
console.log(proxyPourUser.lire()); // OK
proxyPourUser.modifier("Nouveau plan"); // ❌ Refusé
const proxyPourAdmin = new ProxyProtecteur(docSecret, administrateur);
proxyPourAdmin.modifier("Nouveau plan"); // ✅ Autorisé
Proxy distant (Remote Proxy)
Il représente un objet situé dans un espace d’adressage différent (sur un serveur, dans un autre processus). Le proxy se charge de la communication réseau (marshalling/unmarshalling des appels).
// Interface commune (doit être sérialisable)
interface ServiceCalcul {
calculerComplexe(donnees: number[]): Promise<number>;
}
// Proxy distant (côté client)
class ProxyDistant implements ServiceCalcul {
private urlServeur: string;
constructor(url: string) {
this.urlServeur = url;
}
async calculerComplexe(donnees: number[]): Promise<number> {
console.log(`[ProxyDistant] Envoi des données au serveur ${this.urlServeur}...`);
// Simulation d'un appel réseau
const reponse = await this.appelReseau('POST', this.urlServeur + '/calcul', { donnees });
return repese.resultat;
}
private async appelReseau(methode: string, url: string, body: any): Promise<any> {
// Simulation
await new Promise(resolve => setTimeout(resolve, 500)); // Latence réseau
return { resultat: 42 }; // Réponse fictive du serveur
}
}
// Côté client
async function client() {
const service: ServiceCalcul = new ProxyDistant('https://api.calcul.com');
// Pour le client, l'appel est transparent, mais il se passe sur le réseau.
const resultat = await service.calculerComplexe([1, 2, 3]);
console.log(`Résultat reçu : ${resultat}`);
}
// client();
Différence avec Facade
C’est une question cruciale. Tous deux s’interposent, mais leur nature et leur but sont opposés.
| Aspect | Proxy | Facade |
|---|---|---|
| Relation avec l'objet | Représente UN objet unique. Il implémente la même interface et contrôle l’accès à cet objet spécifique. | Cache un SOUS-SYSTÈME (plusieurs objets). Fournit une interface simplifiée à un ensemble de fonctionnalités. |
| Intentions | Contrôle (accès, chargement, coût). Ajoute un comportement autour d’un objet. | Simplification et unification. Réduit la complexité d’un ensemble. |
| Transparence | Souvent transparent. Le client ne sait pas qu’il utilise un proxy (même interface). | Non transparent. Le client sait qu’il utilise une façade, une porte d’entrée simplifiée. |
| Exemples | Image virtuelle, garde de sécurité, stub RPC. | Interface unique pour un home cinéma, un module de paiement complexe. |
| Analogie | Un huissier qui contrôle l’accès à un juge (un objet précis). | Un guichet unique dans une mairie qui regroupe plusieurs services (un sous-système). |
En résumé :
- Un Proxy est un représentant contrôlant d’un objet spécifique.
- Une Facade est une interface simplificatrice pour un ensemble d’objets.
Le Proxy fermait presque la boucle des patterns Structurels. Il était le gardien, le médiateur, l’optimiseur. Là où l’Adapter faisait converser, le Bridge séparait, le Composite unifiait, le Decorator habillait et la Façade simplifiait, le Proxy contrôlait.
Mais il restait un dernier pattern dans la famille structurelle. Le plus économe, le plus discret : le Flyweight. Celui qui partage l’essence pour économiser la mémoire quand les objets se comptent par milliers. Un pattern de survie dans un monde aux ressources limitées.