Bibliothèque · Résumé et avis

Design Patterns

Du Gang of Four. Le catalogue de 1994 qui a nommé les gestes : ce que j'en garde, ce qui est mort, et pourquoi vous parlez GoF sans le savoir.

FR EN
Couverture de Design Patterns, Gang of Four

Design Patterns

Design Patterns: Elements of Reusable Object-Oriented Software

7.2 /10

« Sec comme un manuel, daté comme du C++ de 1994, et pourtant tout le vocabulaire du métier sort de là. »

  • AuteursGamma, Helm, Johnson, Vlissides · le Gang of Four
  • VOAddison-Wesley, 1994 · 395 pages
  • ÉditionÉdition unique (1994), exemples en C++ et Smalltalk
  • Fiche~10 min de lecture
Notation du livre sur 5 dimensionsIdées10/10Applicable7/10Lisibilité5/10Actualité7/10Exemples7/10

Les 23 noms qui ont donné au métier son vocabulaire : vous parlez GoF sans le savoir, comme on fait de la prose.

Pourquoi ce livre

D'abord le décor. Un design pattern (patron de conception) est une solution réutilisable à un problème de conception qui revient sans cesse, avec un nom pour en parler. Ce livre-ci est le catalogue fondateur : 23 patterns, publiés en 1994 par quatre auteurs (Gamma, Helm, Johnson, Vlissides), d'où leur surnom de « Gang of Four », la bande des quatre, et l'abréviation « GoF » que vous croiserez partout. Ma fiche Head First Design Patterns (HFDP, le manuel illustré et ludique qui enseigne ces mêmes patterns) pointait sans arrêt vers ce livre : HFDP est le récit amusant, ici c'est la source.

Ça se lit comme un dictionnaire parce que c'en est un : une entrée par pattern, format fixe. Mais le chapitre 1 et l'étude de cas du chapitre 2 sont de la vraie prose, et ils contiennent les deux principes auxquels tout le reste est suspendu.

Les idées qui restent

1Un pattern, c'est d'abord un nom

Le livre emprunte l'idée à Christopher Alexander, un architecte de bâtiments : un pattern « décrit un problème qui revient sans cesse dans notre environnement, puis décrit le cœur de la solution » (ch. 1). Quatre éléments obligatoires : un nom, un problème, une solution, les conséquences. Et les auteurs insistent : le premier est le plus dur. « Trouver de bons noms a été l'une des parties les plus difficiles de l'élaboration de notre catalogue » (ch. 1).

Pourquoi tant d'histoires pour des noms ? La compression. Avant : « tu sais, l'objet qui en enveloppe un autre et qui ajoute le défilement sans que l'enveloppé le sache ». Après : « un Decorator ». Une discussion de conception perd un niveau d'abstraction chaque fois qu'une structure n'a pas de nom. C'est ça, le vrai produit du livre : pas les techniques (elles existaient déjà), le vocabulaire partagé.

2Les deux commandements auxquels tout est suspendu

Le chapitre 1 pose deux principes en gras, et les 23 patterns en sont des variations.

  • « Programmez vers une interface, pas vers une implémentation » (ch. 1) : ne jamais déclarer une variable comme instance d'une classe concrète ; dépendre du type abstrait, pour que le concret puisse changer dans votre dos.
  • « Préférez la composition d'objets à l'héritage de classes » (ch. 1) : hériter, c'est réutiliser en « boîte blanche » : la sous-classe voit les entrailles du parent, et « tout changement dans l'implémentation du parent forcera la sous-classe à changer ». Composer, c'est réutiliser en « boîte noire » : on assemble des pièces dont on ne voit que l'interface, le contrat.

Les deux commandements, en avant/après sur un cas qu'on a tous écrit :

// ✗ AVANT : classe concrète + héritage pour varier
class Rapport {
  private MySQLExport $export = new MySQLExport();  // concret figé
}
class RapportPdf extends Rapport { ... }
class RapportPdfChiffre extends RapportPdf { ... }   // l'arbre s'emballe

// ✓ APRÈS : une interface + des pièces qu'on assemble
class Rapport {
  public function __construct(
    private Export $export,      // interface : MySQL, CSV, S3...
    private Format $format,      // interface : PDF, HTML...
  ) {}
}
new Rapport(new S3Export(), new PdfChiffre());   // on combine, sans arbre

L'avant fabrique un arbre de classes qui grandit à chaque combinaison (PDF, PDF chiffré, PDF chiffré compressé...). L'après combine des pièces indépendantes : trois exports et trois formats donnent neuf combinaisons pour six classes, et la variante de demain ne casse rien.

L'honnêteté compte : les auteurs observent que « les concepteurs abusent de l'héritage comme technique de réutilisation, et les designs deviennent souvent plus réutilisables (et plus simples) en s'appuyant davantage sur la composition d'objets » (ch. 1). Trente ans de classes de base fragiles leur ont donné raison.

3Une question derrière 23 réponses : qu'est-ce qui doit pouvoir varier ?

Le guide de sélection du livre cache son idée la plus profonde : chaque pattern encapsule une chose qui change, pour que le reste n'ait pas à changer. Cinq exemples, avec à chaque fois la définition puis un cas de tous les jours :

  • Strategy : une famille de calculs interchangeables derrière la même interface ; ce qui est encapsulé, c'est l'algorithme. Vos frais de port : Colissimo, Chronopost ou retrait en magasin, trois calculs différents pour la même question « combien ? », échangeables sans toucher au panier :
    interface FraisDePort { calculer(panier): float }
    class Colissimo      { calculer(panier) { /* grille La Poste */ } }
    class RetraitMagasin { calculer(panier) { return 0 } }
    
    total = panier.total() + livraison.calculer(panier)  // peu importe laquelle
  • State : un objet qui change de comportement selon son état interne, comme s'il « changeait de classe ». Une commande e-commerce : le même bouton « Annuler » ne fait pas du tout la même chose selon que la commande est au panier, payée ou expédiée :
    // la commande PORTE son état : un objet, remplacé à chaque étape de sa vie
    class Commande {
      etat = new AuPanier()                       // état de départ
      payer()    { this.etat = new Payee() }     // changer d'état = changer l'objet
      expedier() { this.etat = new Expediee() }
      annuler()  { this.etat.annuler() }          // délègue à l'état courant
    }
    
    // chaque état sait ce qu'« annuler » veut dire pour lui
    class AuPanier { annuler() { viderLePanier() } }
    class Payee    { annuler() { rembourser() } }
    class Expediee { annuler() { demanderUnRetour() } }
    
    commande.payer()
    commande.annuler()   // → rembourse, car l'état courant est Payee. Zéro if.
  • Iterator : un moyen de parcourir une collection sans connaître sa structure interne :
    // trois sources rangées totalement différemment dessous
    commandes = [101, 102, 103]                  // tableau en mémoire
    commandes = Commande::depuisLaBase()         // résultat SQL, ligne à ligne
    commandes = lireGrosCsv('commandes.csv')    // générateur : jamais tout en RAM
    
    // LA MÊME boucle marche pour les trois : chaque source sait se parcourir elle-même
    foreach (commandes as c) { traiter(c) }
    Le jour où le tableau devient une requête SQL, la boucle ne bouge pas d'une ligne : c'est ça que le pattern achète ;
  • Observer : un objet prévient une liste d'abonnés quand il change, sans les connaître (détaillé à l'idée 5) :
    // l'intérieur du mécanisme : une liste d'abonnés + une boucle, c'est TOUT le pattern
    class Sujet {
      abonnes = []
      abonner(fn)    { this.abonnes.push(fn) }
      notifier(info) { for (fn of this.abonnes) fn(info) }
    }
    
    stock = new Sujet()
    stock.abonner(majPageProduit)      // la page produit veut savoir
    stock.abonner(emailRetourEnStock)  // l'alerte client aussi
    stock.notifier('retour en stock')  // les deux réagissent ; stock ne connaît ni l'un ni l'autre
    
    // votre addEventListener de tous les jours, c'est exactement cette mécanique
    bouton.addEventListener('click', majPanier)
    La newsletter, pareil : l'expéditeur ne sait pas qui est abonné, il envoie, chacun réagit ;
  • Mediator : les objets ne se parlent plus entre eux, ils passent tous par un central qui orchestre. Une conversation de groupe : chacun écrit au salon, personne n'envoie de message à chaque membre un par un :
    // sans médiateur : chaque champ connaît tous les autres (n² liens, l'enfer à modifier)
    
    // avec : les champs ne connaissent QUE le formulaire, qui orchestre
    class Formulaire {
      signaler(quoi) {
        if (quoi === 'date') {
          champHeure.recharger()       // les créneaux dépendent du jour choisi
          boutonValider.desactiver()   // tant que l'heure n'est pas rechoisie
        }
      }
    }
    
    champDate.onChange = () => formulaire.signaler('date')
    // champDate ne sait pas que champHeure existe : seule l'« aiguillage » central le sait

Vous l'avez peut-être remarqué : ces cinq exemples sortent de la même boutique en ligne. C'est volontaire, et c'est la leçon : une seule application réelle convoque naturellement cinq patterns, chacun appelé par son problème (les frais qui varient, la commande qui change d'étape, le stock qui prévient). Surtout, ne faites jamais l'inverse : la « classe Panier ultime » qui empilerait les cinq pour le sport, c'est la fièvre des patterns de l'idée 7. Le chapitre Lexi (idée 4) est la version longue de cette même leçon.

Du coup, la question de travail devant votre propre code n'est jamais « quel pattern puis-je placer ici ? » mais « qu'est-ce qui va changer ? ». Trouvez l'axe de variation, enfermez-le dans un objet, et vous redécouvrez en général un des 23 sans ouvrir le livre.

Alors, le panier idéal, il ressemble à quoi ?

Pas à une classe bardée de patterns : à une petite architecture où chacun occupe le poste exact où il gagne sa place. Le squelette :

// le panier lui-même : AUCUN pattern. La classe la plus centrale est la plus bête.
class Panier { ajouter(produit, qte) ; total() }

interface FraisDePort { calculer(panier) }          // STRATEGY : les frais varient
class FraisOffertsDes implements FraisDePort {       // DECORATOR : la promo s'empile
  __construct(base, seuil) { ... }                    //   sur n'importe quel transporteur
}
class Commande { etat ; annuler() { etat.annuler() } } // STATE : des étapes de vie
class Evenements { abonner() ; publier() }           // OBSERVER : payer déclenche
                                                      //   email, stock... sans les connaître
class Checkout {                                     // FACADE : le seul point d'entrée
  __construct(livraison, evenements) { ... }
  commander(panier) { ... }
}

// l'assemblage : tous les choix concrets se font à la racine, jamais dans les classes
checkout = new Checkout(new FraisOffertsDes(new Colissimo(), 50), evenements)

Trois choses à remarquer. Chaque pattern répond à un problème nommable (les frais varient, la promo s'empile, la commande a des étapes, payer doit déclencher l'inconnu, le contrôleur veut un bouton). La classe centrale n'a aucun pattern. Et les absents sont un choix : pas de Singleton (l'assemblage injecte tout), pas de Command (personne n'a demandé de rejouer pas à pas). Un bon design se reconnaît autant à ses patterns absents qu'à ses patterns présents.

La version pas à pas se construit dans le TD « Construire la boutique, un pattern à la fois », et le récit de sa conception est sur le blog.

4Lexi : huit patterns nés d'un éditeur de documents

Le chapitre 2 est le cœur vivant du livre : les auteurs conçoivent Lexi, un éditeur de documents WYSIWYG (« what you see is what you get » : l'écran montre directement la page finale, comme Word), et chaque problème concret fait naître un pattern, en contexte.

  • un document, c'est du texte dans des lignes dans des colonnes, récursivement → Composite, le pattern qui traite l'élément seul et le groupe d'éléments par la même interface (une classe Glyph pour les caractères comme pour les groupes : on demande « dessine-toi » à une lettre comme à une page) ;
  • les algorithmes de mise en page doivent être interchangeables, y compris celui de TeX (le système de composition typographique de Knuth, réputé pour ses coupures parfaites) → Strategy (le Compositor) ;
  • ajouter une bordure ou une barre de défilement sans tout sous-classer → Decorator, l'enveloppe qui ajoute une responsabilité à un objet sans le modifier, empilable (bordure autour de défilement autour de page) ;
  • supporter plusieurs apparences (Motif et PM, les habillages d'interface des Unix des années 90) → Abstract Factory, l'usine qui fabrique toute une famille d'objets assortis (boutons, menus, barres du même thème) sans que le client connaisse les classes concrètes (« plus rien dans le code ne mentionne Motif par son nom », ch. 2) ;
  • menus, boutons et raccourcis déclenchent la même action, avec annulation → Command, l'action transformée en objet qu'on peut stocker, rejouer ou annuler (un historique avec Execute/Unexecute : c'est le Ctrl+Z) ;
  • vérifier l'orthographe d'une structure sans la polluer → Iterator (parcourir) + Visitor, l'opération qu'on promène sur une structure sans rien lui ajouter (le correcteur visite chaque mot ; demain, le compteur de mots visitera pareil).

Lisez ce chapitre même si vous sautez le catalogue : il montre les patterns comme des réponses à des problèmes ressentis, pas comme des formes à imposer.

5Observer, le pattern qui a mangé le monde

Un objet change ; un nombre ouvert d'autres objets doit suivre, sans que le premier les connaisse. L'exemple du livre : un tableur et un graphique en barres qui affichent les mêmes données, chacun se rafraîchissant quand l'utilisateur modifie l'autre, alors que « le tableur et le graphique ne se connaissent pas » (Observer). Le sujet tient une liste d'abonnés et notifie ; c'est toute la machine. Vous l'écrivez déjà tous les jours sans y penser : bouton.addEventListener("click", maFonction), c'est exactement ça : le bouton est le sujet, votre fonction est l'abonné, et le bouton ne sait rien d'elle.

La conséquence que le livre souligne : le couplage est « abstrait et minimal », donc un sujet de bas niveau peut informer un observateur de haut niveau sans casser les couches. Chaque écouteur d'événement, chaque file pub/sub, chaque état réactif d'interface que vous avez utilisé cette semaine descend de ce chapitre. Le compromis est nommé aussi : push (le sujet envoie le détail, moins réutilisable) contre pull (l'observateur interroge, parfois inefficace).

6Même silhouette, quatre intentions

Adapter, Decorator, Proxy, Facade se ressemblent sur un diagramme : un objet planté devant un autre. Le livre passe ses fins de chapitres à les séparer, parce que ce qui nomme un pattern n'est pas sa forme, c'est son intention :

  • Adapter change l'interface : il fait s'emboîter deux choses qui n'étaient pas prévues l'une pour l'autre (« il fait marcher les choses après leur conception ») ;
  • Decorator garde l'interface et ajoute des responsabilités, empilables à l'exécution ;
  • Proxy garde l'interface et contrôle l'accès (charger l'image au dernier moment, compter les références) ;
  • Facade invente une interface plus simple au-dessus de tout un sous-système (un seul Compile() devant scanner, parseur, générateur).

Le livre est plein de ces coups de scalpel : un decorator change la peau, une strategy change les entrailles ; un MacroCommand est un Composite de Commands ; des objets State partagés « sont essentiellement des flyweights ». Les patterns ne sont pas 23 briques, ce sont 23 intentions qui se combinent.

7Le seul conseil de méthode : commencer simple, évoluer quand ça coince

Le chapitre des créationnels compare ses cinq patterns sur le même programme jouet (un jeu de labyrinthe), et se ferme sur le seul vrai conseil de méthode du livre : « souvent, les designs commencent avec Factory Method et évoluent vers les autres patterns créationnels à mesure que le concepteur découvre où il faut plus de flexibilité » (discussion des créationnels). Traduction : n'installez pas une Abstract Factory au jour 1 ; sous-classez une méthode de création, et montez en gamme quand la réalité le demande.

La même retenue ferme le chapitre 1 : « un design pattern ne devrait être appliqué que lorsque la flexibilité qu'il apporte est réellement nécessaire » (ch. 1). Les auteurs du livre des patterns mettaient en garde contre la fièvre des patterns dès la page 31. Peu de lecteurs sont arrivés jusqu'à cette phrase.

Un jeune développeur fier ouvre une énorme caisse à outils remplie de 23 outils dorés ouvragés, face à une simple planche d'où dépasse un unique petit clou
23 outils dorés, un seul clou. Le livre lui-même prévient : seulement quand la flexibilité est réellement nécessaire.

8Le classique qui s'excuse d'exister

La conclusion s'ouvre sur une phrase qu'aucun service marketing ne laisserait passer : « on peut soutenir que ce livre n'a pas accompli grand-chose » (Conclusion). Pas de nouvel algorithme, pas de théorie : « il ne fait que documenter des conceptions existantes ». Les auteurs revendiquent exactement une contribution : avoir nommé ce que les bons programmeurs faisaient déjà, pour qu'on puisse l'enseigner et en discuter.

Et l'humilité n'est pas une pose. Le livre a mis des années à exister : une thèse, des ateliers, un résumé refusé par une conférence. Même les noms ont changé en route : Singleton s'est d'abord appelé Solitaire, et Facade s'appelait Glue, « la colle ». Leur aveu final : « trouver des patterns est bien plus facile que les décrire » (Conclusion). Quiconque a déjà écrit de la doc sait que c'est vrai.

Trois choses que je ne savais pas

Mon avis, honnêtement

Honnête sur mon niveau d'abord : je suis dev web, je ne passe pas mes journées à citer des livres d'informatique. Les noms, par contre, je les croise depuis toujours : Strategy et Singleton sont dans la doc de Symfony, dans les entretiens, dans les conversations entre devs. Ce livre, c'est l'endroit d'où ils viennent, et je voulais voir la source.

La lecture m'a surpris deux fois. La moitié dictionnaire est vraiment aride : du C++ de 1994, des technologies disparues ; mes yeux ont glissé sur des pages entières, et je l'assume. Mais la moitié prose (le chapitre 1, l'éditeur Lexi, la conclusion) est bien meilleure que sa réputation : claire, humble, et pleine d'avertissements contre la fièvre des patterns qu'elle a elle-même déclenchée.

Ce que j'en retire, à mon niveau : pas les 23 patterns. Je ne les retiendrai pas tous, et vous non plus. Ce qui reste, c'est les deux principes, la question « qu'est-ce qui va changer ? », et surtout les noms. Quand je dis à un assistant IA « extrais ça en Strategy », ça marche du premier coup, parce que le nom compresse toute une conversation. Et une partie du catalogue est morte avec les honneurs : Iterator vit dans le foreach de tous les langages, et dire « Singleton » en entretien est devenu un sport à risque.

Mon conseil honnête : lisez le chapitre 1 et Lexi comme un livre, et gardez le reste comme un dictionnaire qu'on consulte au besoin. Et si vous ne devez en lire qu'un des deux, prenez HFDP, le manuel illustré : il est fait pour apprendre. Celui-ci, c'est le grand-père austère qu'on vient voir pour vérifier la source.

Odilon

Toujours valable en 2026 ?

Le vocabulaire est plus vivant que le livre : c'est l'API partagée entre développeurs, entretiens, docs de frameworks, et maintenant assistants IA, entraînés sur trente ans de textes écrits en GoF. Dire à un agent « transforme ça en Decorator » bat trois paragraphes de description. Ce qui a vieilli : le code C++/Smalltalk, les patterns absorbés par les langages (Iterator, en partie Observer via les primitives réactives), et le style tout-en-classes ; la composition de fonctions règle une partie de ces problèmes avec moins de cérémonie. Les deux principes et la lecture par intention n'ont pas bougé d'un millimètre.

Pour qui ?

Lisez-le si

  • Vous dites Factory, Observer ou Singleton tous les jours et voulez boire à la source
  • Vous concevez du code objet (PHP, Java, C#, TypeScript) et l'héritage vous fait régulièrement mal
  • Vous avez lu Head First Design Patterns et voulez la référence précise et complète
  • Vous relisez du code généré par IA : nommer la structure est le feedback le plus rapide qui soit

Passez votre chemin si

  • Vous attendez un livre qui se dévore : la moitié est une référence à consulter, en C++ de 1994
  • Vous vivez dans du code fonctionnel ou procédural : composer des fonctions remplace la moitié du catalogue
  • Vous débutez en POO : commencez par Head First Design Patterns, revenez ici plus tard

Pour aller plus loin

Les patterns se pratiquent en PHP dans mon cours POO PHP. Côté bibliothèque, Head First Design Patterns raconte le même catalogue avec des blagues et des quiz, PHP 8 Objects & Patterns l'applique à PHP, Clean Architecture bâtit sa Dependency Rule sur le même polymorphisme, et Refactoring donne les pas sûrs pour aller vers (ou s'éloigner d') un pattern.

Commentaires (0)

Voir toute la bibliothèque

D'autres fiches arrivent : un livre à la fois, la substantifique moelle seulement.