Accueil / Tutoriels / Flyweight : optimiser la mémoire intelligemment

Flyweight : optimiser la mémoire intelligemment

Publié le 9 Juin 2025
Conception

Flyweight (poids mouche en français) est un design pattern conçu pour réduire l'utilisation de la mémoire lorsque de nombreux objets similaires sont nécessaires dans une application. Dans le développement logiciel moderne, où performance et consommation de ressources sont des enjeux cruciaux, ce pattern apporte une solution élégante : mutualiser ce qui peut l’être pour ne conserver en mémoire que l’essentiel. Il se montre particulièrement efficace dans les contextes où des objets identiques sont instanciés en masse comme dans le rendu graphique, les éditeurs de texte ou certaines interfaces web.

Principe du pattern Flyweight

Le pattern Flyweight vise à limiter l’usage abusif de la mémoire en partageant des objets identiques au lieu de les dupliquer. Il repose sur un principe simple mais efficace : factoriser les données communes à plusieurs objets, afin que chaque instance ne conserve que ce qui lui est propre.

Au lieu de créer mille fois un objet identique, on en crée un seul que l’on réutilise. C’est ce qu’on appelle un objet « léger » (flyweight). L’objectif est donc de mutualiser le plus possible les données dites « intrinsèques » (communes à tous), tout en laissant les données « extrinsèques » (propres à chaque instance) être manipulées en dehors du flyweight.

Ce pattern est particulièrement utile lorsqu’on manipule un très grand nombre d’objets ayant un état partiellement partagé. Il permet une réduction drastique de l’empreinte mémoire sans pour autant sacrifier la flexibilité du code.

Exemples d’utilisation en programmation web

Optimisation du rendu de texte dans un éditeur en ligne

Pour le rendu de texte dans un éditeur WYSIWYG ou une interface de traitement de texte en ligne, chaque caractère peut être représenté par un objet contenant des informations comme la police, la taille ou le style.

Avec des milliers de caractères identiques, stocker ces attributs à chaque fois serait un gaspillage. Le Flyweight permet ici de partager les propriétés communes entre tous les caractères similaires.

Réutilisation d’icônes et composants visuels dans une interface web

Au niveau du rendu d’icônes ou de composants graphiques récurrents dans une interface web, une bibliothèque de composants peut charger une seule fois chaque icône SVG en mémoire, puis la réutiliser à l’infini à travers la page, sans recréer les objets graphiques à chaque apparition. Cela permet non seulement d’optimiser l’usage mémoire, mais aussi d’accélérer le temps de chargement.

Points d’attention

Flyweight est un outil précieux pour la performance mais son utilisation a certains inconvénients. Il peut complexifier la conception de l’application en introduisant une séparation rigoureuse entre données intrinsèques et extrinsèques, ce qui n’est pas toujours naturel.

Le risque principal réside dans la confusion entre ce qui peut être partagé et ce qui ne doit pas l’être. Mal évaluer cette distinction peut mener à des effets de bord gênants, voire à des bugs liés à des données partagées entre des objets censés être indépendants.

La mise en place initiale d’un Flyweight peut représenter un certain investissement, notamment à cause de la nécessité d’intégrer une factory et un mécanisme de mise en cache efficace. Ce surcoût n’est justifié que si les économies de mémoire et les gains de performance attendus sont significatifs.

Implémenter le pattern Flyweight

L’implémentation du pattern Flyweight repose sur deux concepts clés : la création d’un objet partagé et la gestion d’un mécanisme de réutilisation. En général, cela passe par une factory ou un gestionnaire centralisé (souvent appelé FlyweightFactory) chargé de fournir des instances réutilisables.

Ce gestionnaire conserve un cache d’objets déjà créés, indexés selon leurs données intrinsèques. Lorsqu’un nouvel objet est demandé, la factory vérifie si une instance équivalente existe déjà. Si c’est le cas, elle la renvoie. Sinon, elle la crée, la stocke, puis la retourne.

Le reste des données, spécifiques à chaque cas d’usage, est fourni à part par le client de l’objet, généralement sous forme de paramètres temporaires lors de l’appel à une méthode métier. Ce découplage est au cœur du pattern.

Voici un exemple en Go :

Prenons l’exemple d’un système qui affiche des boutons avec une apparence commune (couleur, forme) mais à des positions différentes dans une interface utilisateur.


package main

import (
   "fmt"
)

// ButtonStyle Flyweight : données partagées
type ButtonStyle struct {
   Color string
   Shape string
}

func (b *ButtonStyle) Draw(x, y int) {
   fmt.Printf("Drawing button at (%d, %d) with color %s and shape %s\n", x, y, b.Color, b.Shape)
}

// ButtonStyleFactory Factory
type ButtonStyleFactory struct {
   styles map[string]*ButtonStyle
}

func NewButtonStyleFactory() *ButtonStyleFactory {
   return &ButtonStyleFactory{styles: make(map[string]*ButtonStyle)}
}

func (f *ButtonStyleFactory) GetStyle(color, shape string) *ButtonStyle {
   key := color + ":" + shape
   if style, exists := f.styles[key]; exists {
      return style
   }

   style := &ButtonStyle{Color: color, Shape: shape}
   f.styles[key] = style

   return style
}

func main() {
   factory := NewButtonStyleFactory()

   button1 := factory.GetStyle("blue", "rounded")
   button2 := factory.GetStyle("blue", "rounded")
   button3 := factory.GetStyle("red", "square")

   button1.Draw(10, 20)
   button2.Draw(30, 40)
   button3.Draw(50, 60)
}

Le même exemple en PHP :


namespace Practice\DesignPatterns\Flyweight;

class ButtonStyle
{
   public string $color;
   public string $shape;

   public function __construct(string $color, string $shape)
   {
       $this->color = $color;
       $this->shape = $shape;
   }

   public function draw(int $x, int $y): void
   {
       echo "Drawing button at ($x,$y) with color {$this->color} and shape {$this->shape}\n";
   }
}


namespace Practice\DesignPatterns\Flyweight;

class ButtonStyleFactory
{
   private array $styles = [];

   public function getStyle(string $color, string $shape): ButtonStyle
   {
       $key = $color . ':' . $shape;
       if (!isset($this->styles[$key])) {
           $this->styles[$key] = new ButtonStyle($color, $shape);
       }
       return $this->styles[$key];
   }
}


declare(strict_types=1);

use Practice\DesignPatterns\Flyweight\ButtonStyleFactory;

require "./vendor/autoload.php";

$factory = new ButtonStyleFactory();

$button1 = $factory->getStyle('blue', 'rounded');
$button2 = $factory->getStyle('blue', 'rounded');
$button3 = $factory->getStyle('red', 'square');

$button1->draw(10, 20);
$button2->draw(30, 40);
$button3->draw(50, 60);

Conclusion

Le pattern Flyweight est redoutable pour les applications web manipulant une grande quantité d’éléments similaires. Bien maîtrisé, il permet de concilier performance et modularité.

Mais comme tout outil puissant, il demande du discernement pour éviter les pièges de la mutualisation excessive. Appliqué judicieusement, il transforme une architecture lourde et gourmande en une mécanique légère et performante.

J'espère que grâce à cet article vous êtes désormais plus à l'aise avec le design pattern Flyweight. Vous pouvez retrouver mon article sur les design patterns ou les autres tutoriels sur Proxy, Strategy, Observer et Abstract Factory.