Comprendre les patrons de conception - Design Patterns
Pourquoi les design patterns sont utiles?
Lorsqu’on développe des applications en PHP orienté objet, on rencontre souvent des problèmes récurrents:
- Comment garantir qu’une classe n’ait qu’une seule instance?
- Comment créer des objets sans exposer la complexité de leur construction?
- Comment notifier automatiquement plusieurs parties d’un système lorsqu’un événement se produit?
Ces difficultés ne sont pas propres à PHP, elles apparaissent dans tous les langages orientés objet.
Pour y répondre, le
Gang of Four (GoF) (quatre auteurs fondateurs) ont proposé en 1994 un
catalogue de 23 patrons de conception regroupés en trois familles (créationnels, structurels et comportementaux). Ces solutions éprouvées permettent de rendre le code plus clair, plus maintenable et surtout plus compréhensible, car elles constituent un langage commun entre développeurs.
Dans cette leçon, nous allons nous concentrer sur 5 patterns essentiels: Singleton, Factory, Observer, Strategy et Adapter. Chacun sera présenté avec son problème, sa définition et un exemple simple en PHP, afin de poser une base solide pour comprendre et appliquer ces concepts.
Singleton: garantir l’unicité d’une instance
Le problème du
Singleton apparaît lorsqu’on veut garantir qu’une classe n’ait qu’une seule instance dans toute l’application. Imaginez par exemple un gestionnaire de configuration ou une connexion à la base de données: il serait dangereux et coûteux d’en créer plusieurs copies. Le Singleton résout ce problème en imposant une seule instance accessible globalement.
En PHP, on implémente ce pattern en rendant le constructeur privé et en fournissant une méthode statique qui retourne toujours la même instance. Ainsi, chaque fois qu’on appelle
getInstance(), on obtient l’unique objet existant:
class Config {
private static $instance = null;
private function __construct() {}
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new Config();
}
return self::$instance;
}
}
$config = Config::getInstance();
Factory Method: simplifier et centraliser la création d’objets
Le problème du
Factory Method est lié à la création d’objets. Dans une application complexe, il est souvent nécessaire de créer des objets de différents types, mais on ne veut pas exposer la logique interne de leur construction. Le Factory Method propose de déléguer cette responsabilité à une méthode spécialisée, appelée "fabrique".
En PHP, on peut définir une classe
ShapeFactory qui se charge de créer les objets selon le type demandé. L’avantage est que le code client n’a pas besoin de connaître les détails de chaque classe, mais il se contente d’appeler la fabrique:
interface Shape {
public function draw();
}
class Circle implements Shape {
public function draw() { echo "Cercle" }
}
class ShapeFactory {
public static function create($type) {
if ($type === "circle") return new Circle();
}
}
$shape = ShapeFactory::create("circle");
$shape->draw();
Observer: notifier automatiquement plusieurs objets lors d’un changement
Le problème du
Observer survient lorsqu’un changement dans un objet doit être automatiquement communiqué à plusieurs autres objets. Par exemple, dans un système de notifications, lorsqu’un utilisateur publie un message, plusieurs abonnés doivent être informés.
Le Observer définit une relation un‑à‑plusieurs: un objet "sujet" garde une liste d’observateurs et les prévient lorsqu’un événement se produit. En PHP, cela se traduit par une classe
Subject qui gère les observateurs et une méthode notify() qui les alerte:
class Subject {
private $observers = [];
public function attach($obs) {
$this->observers[] = $obs;
}
public function notify() {
foreach ($this->observers as $obs)
$obs->update();
}
}
class Observer {
public function update() {
echo "Notification reçue";
}
}
$subject = new Subject();
$subject->attach(new Observer());
$subject->notify();
Strategy: rendre les algorithmes interchangeables et flexibles
Le problème du
Strategy apparaît lorsqu’on veut pouvoir changer facilement d’algorithme sans modifier le code principal. Par exemple, dans un système de calcul de réduction, on peut avoir plusieurs stratégies: réduction en pourcentage, réduction fixe, etc.
Le Strategy encapsule chaque algorithme dans une classe distincte et permet de les remplacer dynamiquement. En PHP, on définit une interface
DiscountStrategy et plusieurs implémentations. Le contexte choisit la stratégie à utiliser et peut la changer à tout moment:
interface DiscountStrategy {
public function apply($price);
}
class PercentageDiscount implements DiscountStrategy {
public function apply($price) {
return $price * 0.9;
}
}
class Context {
private $strategy;
public function __construct(DiscountStrategy $s) {
$this->strategy = $s;
}
public function getPrice($p) {
return $this->strategy->apply($p);
}
}
$context = new Context(new PercentageDiscount());
echo $context->getPrice(100);
Adapter: assurer la compatibilité entre interfaces différentes
Le problème du
Adapter se pose lorsqu’on veut utiliser deux systèmes qui n’ont pas la même interface. Par exemple, une ancienne classe qui ne correspond pas aux besoins actuels, ou une API externe qu’on veut intégrer sans modifier son code.
Le Adapter agit comme un traducteur: il convertit l’interface d’une classe pour la rendre compatible avec une autre. En PHP, on crée une classe intermédiaire qui appelle les méthodes de l’ancien système mais expose une nouvelle interface adaptée:
class OldSystem {
public function request() {
echo "Ancien système";
}
}
class Adapter {
private $old;
public function __construct(OldSystem $o) {
$this->old = $o;
}
public function newRequest() {
$this->old->request();
}
}
$adapter = new Adapter(new OldSystem());
$adapter->newRequest();
Le MVC (Model‑View‑Controller) est l’un des patrons d’architecture les plus utilisés dans le développement web moderne. Il ne fait pas partie des 23 patrons de conception du Gang of Four, mais il s’inspire de leurs principes. Le MVC sera étudié plus en détail dans une leçon ultérieure, car il mérite une présentation approfondie en tant que structure globale d’application.