Decorator
(Décorateur)

But : Pouvoir ajouter dynamiquement des fonctionnalités à un objet.
Fournit une alternative flexible au sous-classage.

Exemple

On a parfois besoin d'ajouter des fonctionnalités à des objets individuels, mais pas à une classe entière.
Par exemple, un framework pour faire des applications graphiques peut avoir besoin de rajouter des bordures ou du scrolling aux composants graphiques.
Hériter d'une bordure par héritage manque de souplesse car l'ajout d'une bordure se fait statiquement, et toutes les instances de la sous-classe auront une bordure ; le client ne peut pas choisir quel composant décorer.

Une approche plus flexible consiste à inclure le composant dans un autre objet qui ajoute la bordure.
Cet autre objet s'appelle un décorateur.
Le décorateur se conforme à l'interface du composant qu'il décore, sa présence est donc transparente pour le client.
Le décorateur fait suivre sa requête au composant, en faisant des choses supplémentaires (avant ou après avoir fait suivre), comme dessiner une bordure.
Comme le processus est transparent, on peut imbriquer des appels à plusieurs décorateurs, ajoutant ainsi autant de fonctionnalités que l'on veut. Exemple GOF de décorateur Diagramme objet qui montre comment composer un composant texte avec deux décorateurs : Decorateur gof Diagramme de classe correspondant : Decorateur gof Le plus important :


Voir le code source de cet exemple dans ExempleGOF.java.

A l'exécution :
java ExempleGOF
=== c1 (pas de décoration) ===
  TextView.draw()

=== c2 (décoration par ScrollDecorator) ===
  ScrollDecorator.addedBehaviour()
  TextView.draw()

=== c3 (décoration par BorderDecorator) ===
  BorderDecorator.addedBehaviour()
  TextView.draw()

=== c4 (décoration par ScrollDecorator et BorderDecorator) ===
  ScrollDecorator.addedBehaviour()
  BorderDecorator.addedBehaviour()
  TextView.draw()
Noter qu'on fait :
VisualComponent c = new BorderDecorator(new ScrollDecorator(new Textview()));
Si on faisait le contraire :
VisualComponent c = new ScrollDecorator(new BorderDecorator(new Textview()));
on aurait le scrolling à l'extérieur de la bordure !

Imaginez cet exemple implémenté avec des sous-classes ; pour permettre toutes les combinaisons, on aurait :
- une superclasse : TextView
- des sous-classes : TextWithBorder, TextWithScroll, TexteWithScrollAndBorder, TextWithBorderAndScroll ...
Le patttern decorator évite cette multiplication de classes.

Structure

Illustration possible

Un composant simple :
Component a = new ConcreteComponent();
a.operation();
Un composant décoré :
Component a = new ConcreteDecoratorA(
                new ConcreteComponent());
a.operation();
Un composant doublement décoré :
Component a = new ConcreteDecoratorB(
                new ConcreteDecoratorA(
                    new ConcreteComponent()));
a.operation();

Diagramme original (gof)

Decorateur gof Traduction en java du diagramme gof dans DiagrammeGOF.java.
Exercice : Décorateur HTML
Exercice : Carrés décorés

Remarques

Décorateurs dans java.io

L'API originale utilise largement Decorator (voir page I / O).

Par exemple :
try{
    LineNumberReader lnr = new LineNumberReader(new BufferedReader(new FileReader("test.txt")));
    String curLine = "";
    int lNum = 0;
    while((curLine = lnr.readLine()) != null){
         lNum =  lnr.getLineNumber();                 
         System.out.println(lNum +"\t     "+ curLine);
     }
}
catch (IOException e) {
    e.printStackTrace();
}
(code dans LineNumberReaderExample.java)

On a bien des décorateurs :
FileReader BufferedReader LineNumberReader
On a le diagramme de classe : Decorateur exemple IO Comme LineNumberReader est déjà un BufferedReader, à la place de :
 LineNumberReader lnr = new LineNumberReader(new BufferedReader(new FileReader("test.txt")));
on aurait pu faire :
 LineNumberReader lnr = new LineNumberReader(new FileReader("test.txt"));

Autre exemple

Cet exemple tiré de stackoverflow illustre d'autres utilisations de Decorator :

On a un objet sérialisé dans un fichier gzippé, et on veut l'utiliser.

On lit le fichier :
FileInputStream fis = new FileInputStream("/path/to/objects.gz");
Pour de meilleures performances, on stocke le résultat en mémoire (voir BufferedReaderCompare.java) :
BufferedInputStream bis = new BufferedInputStream(fis);
Il faut aussi "dé-gzipper" le fichier :
GzipInputStream gis = new GzipInputStream(bis);
Et désérialiser l'objet :
ObjectInputStream ois = new ObjectInputStream(gis);
On peut maintenant utiliser notre objet :
SomeObject someObject = (SomeObject) ois.readObject();
Tout ça aurait pu être écrit en une seule ligne :
ObjectInputStream ois = new ObjectInputStream(
    new GzipInputStream(
        new BufferedInputStream(                         
            new FileInputStream("/objects.gz")
        )                     
    )
);
SomeObject someObject = (SomeObject) ois.readObject();