Accueil / Tutoriels / Composite : simplifier la gestion des structures hiérarchiques en programmation web

Composite : simplifier la gestion des structures hiérarchiques en programmation web

Publié le 9 Juin 2025
Conception

Le design pattern Composite est un grand classique des architectures logicielles robustes. C’est un outil redoutablement efficace pour modéliser des structures hiérarchiques complexes. Arborescences, systèmes de permissions, éléments imbriqués d’un DOM virtuel : autant de cas concrets où ce pattern révèle toute sa puissance.

Si vous êtes développeur web, vous avez probablement déjà rencontré un moment où il fallait traiter des objets à la fois individuels et composites, tout en leur appliquant un traitement uniforme. Le pattern Composite est précisément conçu pour ce genre de problème. Voyons ensemble pourquoi il est pertinent, comment l'utiliser intelligemment.

Principe du pattern Composite

Le pattern Composite repose sur une idée simple : traiter de manière uniforme des objets simples et des groupes d’objets. On définit une interface commune pour les objets individuels et les objets dits composites (ensembles d’éléments, eux-mêmes potentiellement composites).

Cela permet au client de manipuler l’ensemble sans se soucier de la nature exacte de l’élément. On évite ainsi les vérifications if (isComposite) dans le code métier, ce qui le rend plus propre, plus maintenable et ouvert à l’évolution.

Ce pattern est particulièrement utile dès que l’on travaille avec des structures arborescentes ou des compositions récursives, et qu’on souhaite appliquer des opérations de manière uniforme sur ces structures.

Exemples d’utilisation en programmation web

Menus de navigation dynamiques

Un cas très courant est la modélisation d’un menu de navigation dynamique. Chaque entrée peut être un lien simple ou un sous-menu contenant d’autres liens. Avec le pattern Composite, on peut traiter chaque entrée de manière identique : qu’elle soit un lien ou un groupe de liens, on lui applique la méthode render() sans se poser de questions. Cela permet de créer des menus récursifs maintenables et évolutifs, notamment dans les CMS ou les applications à multiples niveaux de navigation.

Systèmes de permissions hiérarchiques

Un autre exemple concerne la gestion des permissions dans une application web. Une permission peut être accordée directement à un utilisateur, ou bien héritée via un groupe d’utilisateurs. Chaque entité (utilisateur, groupe, rôle) peut être traitée comme un composant, et l’application peut interroger l’ensemble de manière récursive pour déterminer les droits effectifs. Cela permet de gérer des règles complexes d’accès sans dupliquer le code métier, tout en conservant une logique claire et unifiée.

Points d’attention

La mise en place du pattern Composite demande un effort initial non négligeable, notamment pour concevoir une hiérarchie propre et bien découplée.

Il peut également y avoir un effet de bord : en imposant une interface commune à tous les éléments, on risque de masquer des comportements spécifiques à certains types d’éléments. Par exemple, un élément de base (comme un lien dans un menu) peut avoir des besoins différents d’un groupe d’éléments (comme un sous-menu). Si ces différences sont importantes, on est parfois obligé de faire des vérifications conditionnelles (if ou switch) pour adapter le comportement, ce qui va à l’encontre du principe même du pattern, qui vise justement à éviter ce genre de distinction explicite.

Enfin, dans des contextes très dynamiques ou peu hiérarchiques, son usage peut devenir contre-productif en alourdissant inutilement le design de l’application.

Implémenter le pattern Composite

Pour implémenter le pattern Composite, on commence par définir une interface commune à tous les composants, qu’ils soient simples ou composites. Cette interface inclura les méthodes que chaque composant doit exposer, comme render(), execute(), ou getName(), selon le cas d’usage.

Ensuite, on implémente cette interface dans deux types de structures : les feuilles (éléments unitaires, sans enfants) et les composites (éléments qui contiennent d’autres composants). Les composites devront stocker une collection de composants et déléguer certaines opérations à leurs enfants.

L’essentiel, c’est que le code qui utilise ces objets puisse les manipuler de la même manière, que l’élément soit simple (comme un paragraphe ou une image) ou composé d’autres éléments (comme un conteneur avec du contenu imbriqué). C’est cette homogénéité d’utilisation qui rend le pattern Composite si puissant et élégant.

Voici un exemple en Go :

Imaginons un générateur de page HTML qui doit afficher une structure de contenu composée de textes, d’images et de conteneurs pouvant eux-mêmes contenir d’autres éléments. Le pattern Composite est idéal dans ce cas là.


package html

import "strings"

type Component interface {
   Render() string
}

type Text struct {
   Content string
}

func (t *Text) Render() string {
   return "" + t.Content + ""
}

type Image struct {
   Src string
}

func (i *Image) Render() string {
   return ""
}

type Container struct {
   Children []Component
}

func (c *Container) Add(child Component) {
   c.Children = append(c.Children, child)
}

func (c *Container) Render() string {
   var sb strings.Builder
   sb.WriteString("")

   for _, child := range c.Children {
      sb.WriteString(child.Render())
   }

   sb.WriteString("")

   return sb.String()
}



package main

import (
   "fmt"
   "designpatterns/composite/html"
)

func main() {
   root := &html.Container{}
   root.Add(&html.Text{Content: "Bienvenue sur mon site"})
   root.Add(&html.Image{Src: "/img/logo.png"})

   subContainer := &html.Container{}
   subContainer.Add(&html.Text{Content: "Sous-section"})
   subContainer.Add(&html.Image{Src: "/img/illustration.jpg"})

   root.Add(subContainer)

   fmt.Println(root.Render())
}

Le même exemple en PHP :


namespace Practice\DesignPatterns\Composite;

interface ComponentInterface
{
   public function render(): string;
}


namespace Practice\DesignPatterns\Composite;

class Text implements ComponentInterface {
   private string $content;

   public function __construct(string $content) {
       $this->content = $content;
   }

   public function render(): string {
       return "{$this->content}";
   }
}


namespace Practice\DesignPatterns\Composite;

class Image implements ComponentInterface {
   private string $src;

   public function __construct(string $src) {
       $this->src = $src;
   }

   public function render(): string {
       return "src}\" />";
   }
}


namespace Practice\DesignPatterns\Composite;

class Container implements ComponentInterface {
   private array $children = [];

   public function add(ComponentInterface $component): void {
       $this->children[] = $component;
   }

   public function render(): string {
       $html = "";
       foreach ($this->children as $child) {
           $html .= $child->render();
       }

       $html .= "";

       return $html;
   }
}


declare(strict_types=1);

use Practice\DesignPatterns\Composite\Container;
use Practice\DesignPatterns\Composite\Image;
use Practice\DesignPatterns\Composite\Text;

require "./vendor/autoload.php";

$root = new Container();
$root->add(new Text("Bienvenue sur mon site"));
$root->add(new Image("/img/logo.png"));

$subContainer = new Container();
$subContainer->add(new Text("Sous-section"));
$subContainer->add(new Image("/img/illustration.jpg"));

$root->add($subContainer);

echo $root->render();

Conclusion

Le pattern Composite vous permettra de créer des structures récursives élégantes et extensibles, tout en gardant une interface uniforme et lisible. Il est parfait pour les structures arborescentes typiques du web (DOM virtuel, composants imbriqués, permissions hiérarchiques…). Mais comme tout outil puissant, il doit être utilisé avec discernement et justifié par la complexité réelle du problème.

Vous pouvez retrouver mon article sur les design patterns ou les autres tutoriels sur Mediator, Decorator, Iterator et Adapter.