Les motifs de conception sont souvent mal compris comme des règles rigides ou des « incantations magiques » garantissant un code évolutif. En réalité, ce sont des solutions éprouvées à des problèmes récurrents en conception logicielle. Cependant, connaître le nom d'un motif n'est que la moitié du combat ; comprendre quand et comment l'appliquer sans sur-ingénierie est là que la séniorité se démontre vraiment. Cet article explore les trois catégories fondamentales des motifs de conception — Créatifs, Structurels et Comportementaux — avec un accent sur l'implémentation pratique plutôt que sur de simples définitions théoriques.
Motifs créatifs : Gérer la création d'objets
Les motifs créatifs traitent des mécanismes de création d'objets, en tentant de créer des objets d'une manière adaptée à la situation. Le plus courant est la Méthode d'usine (Factory Method). Au lieu d'instantier directement des objets, vous demandez à une usine de les créer pour vous. Cela découple le code client des classes concrètes, rendant le système plus extensible.
class NotificationService {
public send() { /* ... */ }
}
class EmailNotification extends NotificationService {
public send() { console.log("Email sent"); }
}
class SMSService extends NotificationService {
public send() { console.log("SMS sent"); }
}
class NotificationFactory {
public static create(type: string): NotificationService {
switch(type) {
case 'email': return new EmailNotification();
case 'sms': return new SMSService();
default: throw new Error("Invalid type");
}
}
}
// Usage
const notifier = NotificationFactory.create('email');
notifier.send();
Cette approche vous permet d'ajouter de nouveaux types de notifications (comme les notifications Push) sans modifier le code client existant, conformément au Principe Ouvert/Fermé.
Motifs structurels : Composer des objets et des classes
Les motifs structurels concernent la manière dont les classes et les objets sont composés pour former des structures plus grandes. Le Motif Décorateur est un favori parmi les développeurs car il vous permet d'étendre la fonctionnalité d'un objet individuel sans modifier sa structure. C'est souvent une meilleure alternative à l'héritage lorsque vous devez ajouter des responsabilités dynamiquement.
interface Coffee {
cost(): number;
description(): string;
}
class BasicCoffee implements Coffee {
cost() { return 1.50; }
description() { return "Black Coffee"; }
}
class MilkDecorator implements Coffee {
private component: Coffee;
constructor(component: Coffee) { this.component = component; }
cost() { return this.component.cost() + 0.50; }
description() { return this.component.description() + " + Milk"; }
}
class SugarDecorator implements Coffee {
private component: Coffee;
constructor(component: Coffee) { this.component = component; }
cost() { return this.component.cost() + 0.10; }
description() { return this.component.description() + " + Sugar"; }
}
const coffee = new SugarDecorator(new MilkDecorator(new BasicCoffee()));
console.log(coffee.description()); // "Black Coffee + Milk + Sugar"
Motifs comportementaux : Communication entre objets
Les motifs comportementaux concernent les algorithmes et l'attribution des responsabilités entre les objets. Le Motif Stratégie vous permet de définir une famille d'algorithmes, d'encapsuler chacun d'entre eux et de les rendre interchangeables à l'exécution. C'est parfait pour les scénarios où le comportement d'un objet doit changer en fonction du contexte, comme différents algorithmes de tri ou méthodes de traitement des paiements.
interface PaymentStrategy {
pay(amount: number): void;
}
class CreditCardPayment implements PaymentStrategy {
pay(amount: number) { console.log(`Paid $${amount} via Credit Card`); }
}
class PayPalPayment implements PaymentStrategy {
pay(amount: number) { console.log(`Paid $${amount} via PayPal`); }
}
class ShoppingCart {
private paymentStrategy: PaymentStrategy;
setPaymentMethod(strategy: PaymentStrategy) {
this.paymentStrategy = strategy;
}
checkout(amount: number) {
this.paymentStrategy.pay(amount);
}
}
Conclusion
Les motifs de conception ne servent pas à montrer son savoir ; ils servent à écrire un code maintenable et flexible. La clé pour les maîtriser est de reconnaître les problèmes qu'ils résolvent avant de chercher la solution elle-même. Au fil de votre carrière, vous constaterez que vous ne faites pas que « utiliser » des motifs, mais que vos décisions architecturales s'alignent naturellement sur eux. Commencez par des implémentations simples, refactorisez vers ces structures lorsque la complexité apparaît, et privilégiez toujours la simplicité à l'astuce.