Le décorateur

Sébastien MERIC

Ajout dynamique de responsabilités à un objet. L'ajout de responsabilités à un objet sonne évidemment comme un héritage. C'est en effet, le moyen le plus évident de lui fournir des responsabilités supplémentaires. Le manque de souplesse de cette méthode (l'héritage) est bien entendu, son inconvénient majeur. Premièrement, l'héritage n'est pas toujours possible. Deuxièmement, l'héritage peut donner lieu à la prolifération de classes. Enfin, les responsabilités supplémentaires ne sont pas dynamiques.

Synonymes : Emballage .

- Titre : Le décorateur

- Auteur : Sébastien MERIC

- Parution : 18 avril 2003

- Mise à jour : 19 juin 2020

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2013 Sébastien MERIC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Exploration

- II. Structure

- III. Implémentation

- IV. Remarque concernant la structure

I - Exploration

L'héritage n'est pas toujours possible.

Vous n'avez en effet pas toujours moyen d'accéder à la classe mère, vous ne l'avez pas nécessairement développée vous-même et elle peut être définie comme étant finale. Il peut aussi arriver que la classe ne soit pas finale, mais qu'en hériter vous rende nerveux à l'idée de devoir maintenir dans toutes vos classes héritières toute modification apportée par l'éditeur de cette classe. Bref que vous ne puissiez réellement pas ou que vous ne désiriez pas, ne change rien à votre problème : il faut éviter d'hériter.

Éviter la prolifération de classes

Imaginons que l'on ait défini une classe dont on veut étendre les capacités. Par exemple nous avons la classe Logger et on veut ajouter à chaque message envoyé vers le logger, l'heure et la date d'émission. Imaginons de plus que nous voulions aussi définir une classe pour un affichage console, un log dans un fichier, un log par mail à un administrateur un affichage SWING ! Dans chaque cas, il faut pouvoir afficher ou non l'heure et la date. Nous voilà donc obligés de surcharger la classe logger (pour un fichier) trois fois pour les divers types de logger et deux fois pour que chacun affiche l'heure et la date, ou non.

Les responsabilités supplémentaires ne sont pas dynamiques

Et le dynamisme peut se représenter par plusieurs choses aussi utiles qu'esthétiques (si tant est que le développement vous paraisse esthétique). Plusieurs points donc :

- le dynamisme de déploiement : c'est donc un fichier de configuration qui vous permet de choisir le mode d'utilisation de vos classes ;

- un dynamisme en runtime : à tout moment vous pouvez basculer du mode horodaté au mode sans horodatage, du mode swing au mode console, etc. ;

- enfin, et on découvre alors là une grande puissance du pattern Décorateur, vous pouvez composer les différentes parties entre elles. Un exemple : Logger classique (donc fichier) associé à LoggerHorodaté (donc horodatage des messages) associé à LoggerSwing (donc affichage écran graphique) donne des résultats dans un fichier et dans un écran, le tout horodaté. Pour faire cela avec l'héritage pur, il m'aurait fallu hériter par exemple de LoggerSwingHorodaté et ajouter les responsabilités pourtant déjà implémentées de log dans un fichier ! Soit encore une classe ;

- enfin, dynamisme de votre application, car l'ajout d'une nouvelle extension devient simple, il suffit d'ajouter une classe tandis que dans la présentation précédente, vous ajoutez une classe multipliée par le nombre de combinaisons requises.

II - Structure

Sur le schéma précédent, nous pouvons étudier la structure requise pour mettre en place le décorateur. Il nous faut une interface racine qui présente l'ensemble des responsabilités de notre panel d'outils. Une classe d'implémentation par défaut évidemment, c'est quand même généralement le cas et ceci permet en plus d'utiliser une fabrique qui renvoie cette classe par défaut si rien ne lui est précisé, ou que la configuration provoque une erreur.

Ensuite il nous faut aussi une racine pour tous les décorateurs qui maintiennent une poignée sur un autre décorateur afin de déléguer une partie de son travail bien entendu.

Le reste du pattern, contient les classes qui implémentent cette « racine » afin d'étendre les responsabilités.

III - Implémentation

Source

/*
* Logger.java
*
* Created on 10 mars 2003, 19:11
*/

package com.developpez.decorateur;

/**
*
* @author  smeric
*/
public interface Logger {

/**
* Log le message.
*/
void log(java.lang.String msg);

}
Source
/*
* DefaultLogger.java
*
* Created on 10 mars 2003, 19:15
*/

package com.developpez.decorateur;

import java.io.*;

/**
* Classe d'implémentation du logger par défaut.
* Cette classe loggue les messages sans fioriture dans un flux qui peut
* être soit un fichier soit la sortie standard.
* @author  smeric pour developpez.com
*/
public class DefaultLogger implements Logger {

/**
* Créer une nouvelle instance de DefaultLogger.<p>
* Cette instance a besoin de connaitre un flux pour écrire à l'intérieur.
*/
public DefaultLogger(PrintStream printSream) {
setPrintStream(writter);
}

/**
* Log le message.
*/
public void log(java.lang.String msg) {
getPrintStream().println(msg);
}

private void setPrintStream(PrintStream value) {
printStream = value;
}

private Writter getPrintStream() {
if (null == printStream) {
return System.out;
}
return printStream;
}

private PrintStream printStream;

}

Source

/*
* LoggerDecorateur.java
*
* Created on 10 mars 2003, 19:32
*/

package com.developpez.decorateur;

/**
*
* @author  smeric
*/
public class LoggerDecorateur implements Logger {

/**
* Pas de constructeur par défaut, il faudra appeler celui-ci.
* Ce constructeur prend en paramètre un autre logger pour enrichir ces possibilités
* et refuse (voir le mutateur) un logger null.
*/
public LoggerDecorateur(Logger logger) {
setLogger(logger);
}

/**
* Log le message.
*/
public void log(String msg) {
getLogger().log(msg);
}

private void setLogger(Logger value) {
assert null != value : "Impossible de décorer un logger null";
logger = value;
}

private Logger logger;
}

IV - Remarque concernant la structure

Si vous vous trouvez face à la problématique exprimée en premier point, la classe mère n'est pas accessible pour l'héritage, il faudra implémenter la classe par défaut sous la forme d'un adaptateur.