Accueil / Tutoriels / Decorator

Decorator

Publié le 3 Mars 2025
Conception

Decorator (Décorateur en français) est un pattern structurel qui permet de modifier le comportement initial d’un objet, mais sans en modifier sa structure. Ce pattern favorise l’utilisation de la composition plutôt que l’héritage d’une classe parente.

Le principe

Au lieu de créer une sous classe à partir d’une classe parente, on va encapsuler l’objet original dans un ou plusieurs "décorateurs”. Ces décorateurs implémentent la même interface que l’objet de base et vont ajouter de nouveaux comportements/fonctionnalités.

Les fonctionnalités sont ajoutées de façon dynamique sans avoir à modifier la classe parente. On évite ainsi de se retrouver avec une pléthore de classes avec des combinaisons diverses et variées de responsabilités. Chaque décorateur ayant une responsabilité spécifique, on obtient un meilleur découplage ce qui rend la réutilisation et les combinaisons de décorateurs très faciles.

Pour ceux qui sont familier avec les principes SOLID, le pattern respecte ce principe d’avoir des classes ouvertes à l’extension, mais fermées à la modification (open/closed principle).

Certains remarqueront des similitudes entre un décorateur et une chaîne de responsabilité. Les deux patterns partagent beaucoup de points communs, comme la modularité et la séparation des responsabilités. Toutefois, la chaîne de responsabilités va plutôt transmettre une requête d’un objet à un autre, tandis que le décorateur va encapsuler un objet pour l’enrichir en fonctionnalités.

Exemples d’utilisations

Mise en forme d’un texte

On part d’un objet représentant un texte sans style ou formatage. Les décorateurs de cet objet pourront être des objets ajoutant de la mise en gras, du texte en italique, de la couleur, du surlignage, etc…

Mise en cache du résultat d’une requête SQL

Pour éviter des appels répétitifs à une base de données qui peuvent être coûteux en ressources et performance, on peut utiliser un décorateur qui intercepte la requête et retourne le résultat de cette requête mis en cache (si le cache existe déjà).

Points d’attentions

Si un objet est encapsulé dans plusieurs décorateurs, il peut être difficile de retracer le flux d'exécution complet car chaque couche va ajouter son propre traitement. Le débogage peut s’avérer difficile si on utilise beaucoup de décorateurs sur le même objet.

Le nombre et l'ordre dans lequel les décorateurs sont exécutés peut entraîner des comportements inattendus, des conflits entre les différentes fonctionnalités des décorateurs, et des problèmes de performances.

Chaque décorateur correspond à une classe qui va fournir une fonctionnalité particulière. Un projet peut se retrouver avec un nombre important de ces classes, ce qui rend la prise en main ou la compréhension globale du code. Attention ne pas abuser des décorateurs.

Implémenter un décorateur

La classe de base et les décorateurs implémentent la même interface. Au lieu de créer des classes qui vont hérités de la classe parente, le décorateur encapsule l’objet de base en conservant une référence à celui-ci. On va construire des objets en combinant d’autres objets qui encapsulent chacun une fonctionnalité.

Le décorateur peut ensuite effectuer des traitements avant ou après l’appel à l’objet de référence et transmettre ensuite cet appel, ce qui permet d’empiler plusieurs décorateurs.

Exemple d'utilisation d'un Decorator avec Go

Voici un exemple en Go :

On définit l'interface d'une source de données.


package data


type DataSource interface {
   WriteData(data string)
   ReadData() string
}

L'implémentation de base qui stocke les données.


package data


type FileDataSource struct {
   data string
}


func (f *FileDataSource) WriteData(data string) {
   f.data = data
}


func (f *FileDataSource) ReadData() string {
   return f.data
}


On ajoute une couche de chiffrement. Ici, le chiffrement est simulée en inversant la chaîne de caractères.


package decorators


import (
   "fmt"
   "practice/designpatterns/decorator-decorticode/data"
)


type EncryptionDecorator struct {
   Source data.DataSource
}


func (e *EncryptionDecorator) WriteData(data string) {
   encryptedData := reverse(data)
   fmt.Println("Données chiffrés : ", encryptedData)


   e.Source.WriteData(encryptedData)
}


func (e *EncryptionDecorator) ReadData() string {
   encryptedData := e.Source.ReadData()
   decryptedData := reverse(encryptedData) // On "déchiffre" en inversant à nouveau


   fmt.Println("Données déchiffrés : ", decryptedData)


   return decryptedData
}


func reverse(s string) string {
   runes := []rune(s)
   for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
      runes[i], runes[j] = runes[j], runes[i]
   }


   return string(runes)
}

Décorateur pour ajouter des logs.


package decorators


import (
   "log"
   "practice/designpatterns/decorator-decorticode/data"
)


type LoggingDecorator struct {
   Source data.DataSource
}


func (l *LoggingDecorator) WriteData(data string) {
   log.Printf("Écriture dans un fichier de logs : %s", data)
   l.Source.WriteData(data)
}


func (l *LoggingDecorator) ReadData() string {
   readData := l.Source.ReadData()
   log.Printf("Lecture du fichier de logs : %s", readData)


   return readData
}


package main


import (
   "fmt"
   "practice/designpatterns/decorator-decorticode/data"
   "practice/designpatterns/decorator-decorticode/decorators"
)


func main() {
   file := &data.FileDataSource{}


   // Mise en place du décorateur de chiffrement sur la source de données.
   encryptedDataSource := &decorators.EncryptionDecorator{Source: file}


   // Mise en place du décorateur de logging sur le décorateur de chiffrement.
   decoratedDataSource := &decorators.LoggingDecorator{Source: encryptedDataSource}


   // Écriture des données
   decoratedDataSource.WriteData("Je veux chiffrer cette phrase")


   // Lecture des données
   result := decoratedDataSource.ReadData()


   fmt.Println("Fin du traitement : ", result)
}

Le même exemple en PHP

Voici exactement le même exemple mais avec PHP :


namespace Practice\DesignPatterns\Decorator;


interface DataSourceInterface
{
   public function writeData(string $data): void;
   public function readData(): string;
}


namespace Practice\DesignPatterns\Decorator;


class FileDataSource implements DataSourceInterface {
   private string $data = "";


   public function writeData(string $data): void {
       $this->data = $data;
   }


   public function readData(): string {
       return $this->data;
   }
}


namespace Practice\DesignPatterns\Decorator;


class EncryptionDecorator implements DataSourceInterface {
   private DataSourceInterface $source;


   public function __construct(DataSourceInterface $dataSource) {
       $this->source = $dataSource;
   }


   public function writeData(string $data): void {
       $encryptedData = $this->reverse($data);


       echo "Données chiffrés : " . $encryptedData;


       $this->source->writeData($encryptedData);
   }


   public function readData(): string {
       $encryptedData = $this->source->readData();


       echo "Données déchiffrés : " . $encryptedData;


       return $this->reverse($encryptedData);
   }


   private function reverse(string $text): string {
       return strrev($text);
   }
}


namespace Practice\DesignPatterns\Decorator;


class LoggingDecorator implements DataSourceInterface {
   private DataSourceInterface $source;


   public function __construct(DataSourceInterface $dataSource) {
       $this->source = $dataSource;
   }


   public function writeData(string $data): void {
       echo "Écriture dans un fichier de logs : " . $data;
       $this->source->writeData($data);
   }


   public function readData(): string {
       $data = $this->source->readData();
       echo "Lecture du fichier de logs : " . $data;


       return $data;
   }
}


declare(strict_types=1);


use Practice\DesignPatterns\EncryptionDecorator;
use Practice\DesignPatterns\FileDataSource;
use Practice\DesignPatterns\LoggingDecorator;


require "./vendor/autoload.php";




// Utilisation :
// 1. Création de la source de données de base.
$fileDataSource = new FileDataSource();


// 2. Application du décorateur d'encryption.
$encryptedDataSource = new EncryptionDecorator($fileDataSource);


// 3. Application du décorateur de logging sur le décorateur d'encryption.
$decoratedDataSource = new LoggingDecorator($encryptedDataSource);


// Écriture des données via la chaîne de décorateurs.
$decoratedDataSource->writeData("Je veux chiffrer cette phrase");


// Lecture des données via la chaîne de décorateurs.
$result = $decoratedDataSource->readData();


echo "Fin du traitement : " . $result . "\n";

Conclusion

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