Chapitre 5.4.9Les design patterns

Des solutions éprouvées aux problèmes récurrents.

3 minutes de lecture

Les développeurs rencontrent sans cesse les mêmes problèmes. Comment créer un objet complexe ? Comment s'assurer qu'il n'y a qu'un seul exemplaire d'un composant ? Comment organiser les interactions entre l'interface et la logique métier ? Plutôt que de réinventer la roue, la communauté a formalisé des solutions éprouvées. Ce sont les patrons de conception, ou en anglais design patterns.

Un design pattern est une recette, une façon connue d'agencer des objets pour résoudre un problème récurrent. Elle s'applique dans de nombreuses situations et permet d'obtenir un code bien architecturé, facile à faire évoluer et à tester.

Le Gang of Four

En 1994, quatre auteurs publient un livre devenu une bible du développement : "Design Patterns: Elements of Reusable Object-Oriented Software". Ces quatre auteurs sont surnommés le Gang of Four (GoF). Leur livre catalogue 23 design patterns classés en trois catégories :

  • Créationnels : comment créer des objets de manière flexible. Par exemple, le pattern Builder, ou monteur, qui construit un objet complexe étape par étape plutôt que d'un seul coup. Imagine que tu programmes un robot pizzaiolo. Le monteur part d'une pâte, ajoute la sauce, le fromage, puis chaque garniture commandée, et ne livre la pizza qu'une fois toutes les étapes terminées. Deux commandes différentes passent par les mêmes étapes, sans qu'il faille programmer à l'avance chaque combinaison possible.
  • Structurels : comment assembler des petites briques pour en faire des structures plus complexes tout en gardant le système flexible. Par exemple, le pattern Decorator, qui emballe un objet dans un autre pour lui ajouter un comportement. Dans le code d'un traitement de texte, le mot est un objet, que l'on emballe dans un objet qui le met en gras, puis dans un autre qui le met en italique. Chaque mise en forme s'ajoute par-dessus la précédente.
  • Comportementaux : comment les objets communiquent et se partagent les responsabilités. Par exemple, le pattern Observer : un objet prévient automatiquement tous les intéressés quand quelque chose change. Sur une boutique en ligne, le produit qui revient en stock prévient tous les clients qui ont demandé une alerte, sans avoir besoin de savoir qui ils sont.

Les design patterns pour débuter

Tu n'as aucune raison de connaître les 23 patterns du livre. En revanche, quelques noms reviennent régulièrement dans la bouche des équipes, tous ne venant d'ailleurs pas du Gang of Four.

Le Singleton

Le pattern le plus simple et le plus controversé. Un Singleton garantit qu'il n'existe qu'une seule instance d'un objet dans tout le programme.

Le cas d'école, c'est la connexion à la base de données. Tu ne veux pas ouvrir une nouvelle connexion à chaque requête, tu veux réutiliser la même.

Pour y arriver, le Singleton empêche le reste du programme de créer l'objet lui-même. Le constructeur, cette fonction vue dans le chapitre sur la programmation objet qui s'exécute à chaque new, est déclaré privé. Il ne peut alors être appelé que depuis l'intérieur de la classe, qui crée la connexion la première fois, puis renvoie toujours la même.

Un Singleton en PHP
class ConnexionBaseDeDonnees
{
    // static : la connexion est partagée par tout le programme,
    // elle n'appartient pas à un objet en particulier
    private static ?ConnexionBaseDeDonnees $connexion = null;

    // Le constructeur est privé, personne ne peut faire un new
    // en dehors de la classe
    private function __construct()
    {
    }

    public static function obtenir(): ConnexionBaseDeDonnees
    {
        if (self::$connexion === null) {
            self::$connexion = new ConnexionBaseDeDonnees();
        }

        return self::$connexion;
    }
}

$a = ConnexionBaseDeDonnees::obtenir(); // crée la connexion
$b = ConnexionBaseDeDonnees::obtenir(); // renvoie la même, $a et $b sont identiques
$c = new ConnexionBaseDeDonnees();      // erreur, le constructeur est privé

Pourquoi controversé ? Parce qu'il est souvent mal utilisé. Les débutants en mettent partout, ce qui revient à créer des variables globales déguisées, et rend le code difficile à tester et à faire évoluer.

MVC : Model View Controller

Le MVC est un grand classique des interfaces graphiques. Il ne fait pas partie des 23 du Gang of Four et leur est même antérieur, puisqu'il a été imaginé à la fin des années 1970 par le Norvégien Trygve Reenskaug. Il sépare une application en trois composants :

  • Le Modèle : les données et la logique métier. "Un client a un nom, un email, et peut passer une commande."
  • La Vue : ce que l'utilisateur voit. La page web, le formulaire, le tableau de bord.
  • Le Contrôleur : le chef d'orchestre qui reçoit les actions de l'utilisateur, les transmet au modèle et met à jour la vue.

Quand tu cliques sur "Ajouter au panier" sur un site e-commerce, le contrôleur reçoit l'action, demande au modèle d'ajouter le produit, puis met à jour la vue pour afficher le nouveau contenu du panier.

Cette séparation permet à un développeur front-end de modifier l'interface sans toucher à la logique métier, et à un développeur back-end de changer la façon dont les données sont stockées sans impacter ce que l'utilisateur voit.

Aujourd'hui, on utilise de nombreuses variantes du MVC. Selon le contexte, on découpe l'application un peu différemment. Côté front-end par exemple, une interface est découpée en composants (un bouton, une carte produit, un panier), chacun portant son affichage et sa petite logique. Ce n'est plus tout à fait du MVC, mais l'idée reste la même, ne jamais mélanger ce qui s'affiche, ce qui décide et ce qui stocke.

D'autres design patterns courants

  • Repository : une couche qui isole l'accès aux données. Le reste du code demande "donne-moi le client 42" sans savoir si ça vient d'une base de données, d'un fichier ou d'une API. Celui-là ne vient pas du livre du Gang of Four mais de Martin Fowler, un autre auteur incontournable du domaine.
  • Strategy : plusieurs façons de faire la même chose, interchangeables. Par exemple, pour les frais de port, le programme décide dynamiquement au moment de la commande quel calcul appliquer selon le transporteur choisi.
  • Adapter : un traducteur entre deux composants qui ne parlent pas la même langue. Typiquement pour brancher un service externe sans contaminer tout le code avec son vocabulaire à lui.

Exemple :

Ton entreprise change de prestataire d'envoi d'emails. Si le code appelle directement le premier prestataire partout, il faut modifier des dizaines de fichiers. Avec un Adapter, une seule brique change et c'est transparent pour tout le reste du code.

Les anti-patterns

Si les design patterns ont été formalisés et largement diffusés, c'est pour éviter que chaque développeur invente sa propre architecture dans son coin. Laissé à lui-même, il finit souvent par produire des conceptions bancales, difficiles à tester et de plus en plus coûteuses à maintenir. Ces erreurs de conception reviennent si souvent qu'elles portent un nom, les anti-patterns, et ce sont elles qu'une équipe évoque quand elle explique pourquoi elle avance lentement.

  • Le plat de spaghetti : tout appelle tout, personne ne sait par où commencer, et modifier une ligne casse quelque chose à l'autre bout.
  • L'objet fourre-tout : une classe de trois mille lignes qui gère la commande, le paiement, l'email et la facture. Celle que tout le monde évite d'ouvrir.
  • Le copier-coller : la même règle recopiée à dix endroits. Le jour où la TVA change, on en corrige neuf.

Pourquoi c'est important ?

Les design patterns sont un vocabulaire commun entre développeurs. Quand quelqu'un dit "j'ai mis en place un Observer", tout le monde comprend immédiatement le mécanisme, sans avoir besoin d'une explication détaillée.

Comme tous les outils, les design patterns sont utiles quand ils résolvent un vrai problème. Les utiliser pour le plaisir d'utiliser un pattern, c'est de la sur-ingénierie. Le piège est classique, on apprend les patterns et on veut les appliquer partout, même là où un simple if aurait suffi.

Le pattern posé trop tôt, "au cas où", coûte aussi cher que le pattern absent. Il ajoute des couches que personne ne traverse, et il finit dans la dette technique au même titre que le code bâclé, avec le désavantage d'avoir l'air propre.

L'expérience, c'est savoir quand et comment utiliser un pattern. Et comme en cuisine, un développeur aguerri peut s'autoriser quelques ajustements sur la recette, pour obtenir un plat unique qui répond exactement à son besoin.

PrécédentLa programmation objet Tous les chapitres SuivantLes principes SOLID