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▲
/*
* 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;
}
/*
* 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.