Utilisation du pattern Décorateur
Les programmeurs Java savent qu’ils peuvent modifier le comportement ou étendre les fonctionnalités d’une classe par extension. Cela s’appelle l’héritage, et il s’agit d’un concept très important de la programmation orientée-objet. Par exemple, si vous voulez un label Swing qui dessine une bordure, vous pouvez sous-classer la classe javax.swing.JLabel. Cependant, le sous-classement n’est pas toujours approprié. Parfois l’héritage n’est pas pratique et vous devez trouver une autre manière de faire, par exemple en utilisant le pattern Décorateur. Cet article explique le pattern Décorateur, et quand utiliser le sous-classement ou quand décorer. Les classes utilisées dans cette application se trouvent dans un package appelé decorator et peuvent être téléchargées ici.
Le langage Java fourni le mot-clé extends pour le sous-classement. Ceux d’entre vous qui ont suffisamment d’expérience en programmation orientée-objet connaissent la puissance du sous-classement. En sous-classant une classe, vous pouvez modifier son comportement. Prenez par exemple ma classe JBorderLabel dans le Listing 1. La nouvelle classe appelée JBorderLabel, est une extension de la classe javax.swing.JLabel. Elle ressemble et se comporte comme la classe JLabel, en ajoutant une bordure.
Listing 1 — la classe JBorderLabel, un exemple de sous-classement
package decorator;
import java.awt.Graphics;
import javax.swing.JLabel;
import javax.swing.Icon;
public class JBorderLabel extends JLabel {
public JBorderLabel() {
super();
}
public JBorderLabel(String text) {
super(text);
}
public JBorderLabel(Icon image) {
super(image);
}
public JBorderLabel(String text, Icon image, int horizontalAlignment) {
super(text, image, horizontalAlignment);
}
public JBorderLabel(String text, int horizontalAlignment) {
super(text, horizontalAlignment);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int height = this.getHeight();
int width = this.getWidth();
g.drawRect(0, 0, width - 1, height - 1);
}
}
Pour comprendre comment JBorderLabel fonctionne, vous devez savoir comment Swing dessine ses composants.
La classe JLabel, comme tous les composants Swing, dérive de javax.swing.JComponent. Swing appelle la méthode paint de la classe JComponent pour dessiner son interface utilisateur. Pour modifier la façon dont le composant peint son interface, surchargez la méthode publique paint. Voici la signature de la méthode paint de JComponent.
public void paint(Graphics g)
L’objet Graphics que la méthode paint reçoit représente la surface de dessin. Pour optimiser le dessin, la méthode paint est divisée en trois méthodes protégées : paintComponent, paintBorder et paintChildren.paint font appel à ces méthodes, leur passant l’instance de Graphics qu’elles ont reçue.
protected void paintComponent(Graphics g)
protected void paintBorder(Graphics g)
protected void paintChildren(Graphics g)
Vous pouvez personnaliser le dessin d’un composant Swing en surchargeant l’une ou toutes ces méthodes — et même la méthode paint.
La classe JBorderLabel surcharge la méthode paintComponent de javax.swing.JComponent.
La méthode paintComponent de JBorderLabel appelle premièrement la méthode paintComponent de sa classe parente, ce qui à pour effet de dessiner un JLabel.
Elle obtient ensuite sa hauteur et sa largeur, dessine un rectangle par la méthode drawRect de l’instance de java.awt.Graphics.
La Figure 1 montre une instance de la classe JBorderLabel comme le premier composant de l’image.
Comme vous pouvez le voir, cela ressemble à JLabel, mais avec une bordure.
Bien que le sous-classement fonctionne bien ici, il est des cas pour lesquels il n’est pas approprié ou pratique.
Si vous souhaitez ce comportement (c’est-à-dire dessiner une bordure) pour d’autres composants, alors vous vous retrouvez avec beaucoup de sous-classes.
Dans le Listing 1, le sous-classement semble simple, parce qu’il ne surcharge qu’une seule méthode.
Cependant, au fur et à mesure que votre code grandit, avoir trop de sous-classes entraîne des problèmes de maintenance et rend votre code plus facilement sujet aux erreurs.
(Vous devez également reproduire tous les constructeurs que votre sous-classe doit supporter, comme dans la classe JBorderLabel.)
Dans de tels cas, il est préférable d’utiliser le pattern Décorateur.
Le Pattern Décorateur
Le livre Design Patterns : Elements of Reusable Object-Oriented Software, par Erich Gamma et al, classifie le pattern Décorateur comme un pattern de structure.
Ce pattern offre une alternative flexible au sous-classement.
La principale différence entre le sous-classement et le pattern Décorateur est celle-ci : en sous-classant, vous travaillez avec une classe, alors qu’avec le pattern Décorateur, vous modifiez les objets dynamiquement.
Quand vous sous-classez une classe, les modifications que vous apportez dans la classe enfant affectent toutes les instances de cette classe enfant.
En revanche, avec le pattern Décorateur, vous appliquez les modifications uniquement aux instances que vous souhaitez.
Lorsque vous écrivez une classe décorateur pour modifier l’interface utilisateur de composants Swing, il est impératif de comprendre la classe JComponent.
J’ai déja expliqué comment JComponent peint son interface utilisateur dans la section précédente, et vous pouvez lire la documentation pour tous les membres de cette classe.
Faites attention au fait que JComponent peut avoir des composants enfants ; ces enfants seront peints lorsque JComponent est peint.
Créez un décorateur Swing en sous-classant JComponent avec un constructeur qui accepte un JComponent.
Passez n’importe quel objet Swing dont le comportement a besoin d’être modifié par le décorateur.
A l’intérieur de la classe décorateur, ajoutez le composant en tant que composant enfant.
Au lieu d’ajouter un composant Swing à JFrame ou JPanel ou d’autres conteneurs, passez le au décorateur et ajouter le décorateur à JFrame ou JPanel.
Comme le décorateur est également un JComponent, le conteneur ne verra pas la différence.
Il en résulte que le décorateur est maintenant un composant enfant du conteneur.
Quand le conteneur donne la main au décorateur pour se dessiner, le décorateur invoque sa méthode paint.
Par exemple, supposez que vous avez un JLabel que vous souhaitiez ajouter à un JFrame appelé frame1. Vous utilisez un code semblable à :
frame.getContentPane().add(new JLabel("a label"));
Pour décorer votre JLabel avec MyDecorator, le code est similaire. (Souvenez-vous que le constructeur de la classe MyDecorator accepte un JComponent) :
frame.getContentPane().add(new MyDecorator(new JLabel("a label")));
Cet article propose deux exemples du pattern Décorateur. Le premier s’appelle BorderDecorator. Cette classe est utilisée pour décorer un JComponent afin qu’il ait une bordure. Ajouté à un JFrame, un JLabel qui a été décoré par BorderDecorator est semblable à une instance de JBorderLabel ; cependant, aucun sous-classement n’est nécessaire. Mieux encore, vous pouvez passer n’importe quel composant Swing à BorderDecorator et tous recevront une bordure. Dans cet exemple, vous créez une classe (BorderDecorator) pour modifier le comportement d’instances de différents types. Le second exemple est ResizableDecorator. Ce décorateur ajoute un petit bouton dans le coin gauche de tout composant Swing qui lui est passé. Quand l’utilisateur clique sur le bouton, le composant est minimisé au niveau du bouton.
La Classe BorderDecorator
Commençons par BorderDecorator. Cette classe représente un décorateur qui ajoute une bordure à un composant Swing. Voyez le Lising 2 pour son code. Listing 2 — la classe BorderDecorator
package decorator;
import javax.swing.JComponent;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.BorderLayout;
public class BorderDecorator extends JComponent {
// composant décoré
protected JComponent child;
public BorderDecorator(JComponent component) {
child = component;
this.setLayout(new BorderLayout());
this.add(child);
}
public void paint(Graphics g) {
super.paint(g);
int height = this.getHeight();
int width = this.getWidth();
g.drawRect(0, 0, width - 1, height - 1);
}
}
Premièrement, vous remarquez que BorderDecorator sous-classe JComponent et que son constructeur accepte un JComponent à décorer. La classe BorderDecorator définit aussi un JComponent appelé child qui pointe sur le JComponent décoré.
Le constructeur assigne le composant décoré à child et l’ajoute à un composant enfant du décorator.
Remarquez qu’ici, nous utilisons un BorderLayout pour l’arrangement du décorateur.
Cela signifie que le JComponent ajouté occupera toute la surface du décorateur.
Maintenant, regardez la méthode paint. Elle fait d’abord appel à la méthode paint de la classe parente.
Ceci a pour effet de peindre le décorateur ainsi que tous ces enfants.
Nous dessinons ensuite un rectangle autour de la surface du décorateur, après l’obtention de sa hauteur et de sa largeur.
La Figure 1 montre un JFrame avec trois composants :
- Une instance de JBorderLabel
- Un JLabel décoré
- Un JCheckBox décoré

Figure 1 — Comparaison du sous-classement et du pattern Décorateur
Comme la Figure 1 le montre, il n’y a pas de différence visible entre une instance de JBorderLabel et un JLabel décoré.
Ceci démontre que le pattern Décorateur peut être utilisé comme une alternative au sous-classement.
Le troisième composant démontre que vous pouvez utiliser le même Décorateur pour étendre le comportement d’une instance d’une classe différente.
Sur cet aspect, le décorateur est plus intéressant, parce qu’il vous suffit de créer une seule classe (BorderDecorator) pour étendre la fonctionnalité d’objets de type différents.
Le Listing 3 vous montre le code de JFrame dans la Figure 1.
Listing 3 — Utilisation de la classe BorderDecorator
package decorator;
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
public class Frame1 extends JFrame {
JBorderLabel label1 =
new JBorderLabel("JLabel Subclass");
BorderDecorator label2 =
new BorderDecorator(new JLabel("Decorated JLabel"));
BorderDecorator checkBox1 =
new BorderDecorator(new JCheckBox("Decorated JCheckBox"));
public Frame1() {
try {
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
getContentPane().setLayout(null);
label1.setBounds(new Rectangle(10, 10, 120, 25));
this.getContentPane().add(label1, null);
label2.setBounds(new Rectangle(10, 60, 120, 25));
this.getContentPane().add(label2, null);
checkBox1.setBounds(new Rectangle(10, 110, 160, 25));
this.getContentPane().add(checkBox1, null);
}
catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Frame1 frame1 = new Frame1();
frame1.setBounds(0, 0, 200, 200);
frame1.setVisible(true);
}
}
La Classe ResizableDecorator
ResizableDecorator est un autre type de décorateur.
Il ne surcharge pas la méthode paint de sa classe parente ; au lieu de cela, il ajoute un petit bouton qui, une fois cliqué, redimensionne le composant.
Le Listing 4 présente le code de la classe ResizableDecorator
Listing 4 — La classe ResizableDecorator
package decorator;
import javax.swing.*;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.Rectangle;
public class ResizableDecorator extends JComponent {
// composant décoré
protected JComponent child;
private JButton button = new JButton();
boolean minimum = false;
private Rectangle r;
public ResizableDecorator(JComponent component) {
child = component;
this.setLayout(new BorderLayout());
this.add(child);
child.setLayout(null);
button.setBounds(0, 0, 8, 8);
child.add(button);
button.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(ActionEvent e) {
button_actionPerformed(e);
}
});
}
void button_actionPerformed(ActionEvent e) {
if (minimum) {
this.setBounds(r);
}
else {
r = this.getBounds();
this.setBounds(r.x, r.y, 8, 8);
}
minimum = !minimum;
}
}
Premièrement, remarquez que ce décorateur étend JComponent, et son constructeur accepte un JComponent.
En plus d’un attribut appelé child pour pointer sur un composant décoré, il possède également un attribut JButton.
Les trois premières lignes de son constructeur ajoute le composant
décoré en tant que composant enfant du décorateur.
child = component;
this.setLayout(new BorderLayout());
this.add(child);
Il ajoute ensuite le JButton comme un composant enfant du composant décoré. La disposition (layout) de l’enfant est null. Le JButton aura une dimension de 8 par 8 pixels et est ajouté dans le coin gauche du composant décoré.
child.setLayout(null);
button.setBounds(0, 0, 8, 8);
child.add(button);
Enfin, il ajoute un listener à JButton.
button.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(ActionEvent e) {
button_actionPerformed(e);
}
});
Quand il est ajouté à un conteneur, le composant décoré utilisant ResizableDecorator aura un bouton dans son coin gauche. S’il est cliqué, le bouton exécutera le code de la méthode button_actionPerformed. la méthode vérifie premièrement la valeur de minimum. Ce booléen indique si la taille du décorateur est à son minimum ou non. A l’initialisation, la valeur est false, donc le bloc else est exécuté.
else {
r = this.getBounds();
this.setBounds(r.x, r.y, 8, 8);
}
Il assigne les dimensions actuelles à Rectangle r et initialise les bords à 8 par 8, en laissant le coin gauche en position.
Puis il change la valeur de minimum.
minimum = !minimum;
Lorsque le JButton est cliqué une seconde fois, la valeur de minimum est mise à true. Cette fois, c’est le bloc if qui est exécuté.
if (minimum) {
this.setBounds(r);
}
Le code restaure le décorateur à sa taille précédente.
La Figure 2 montre un JFrame avec trois composants décorés, et son code est donné par le Listing 5.

Figure 2 — Trois composants décorés utilisant ResizableDecorator
Listing 5 — Utilisation de la classe ResizableDecorator
package decorator;
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
public class Frame2 extends JFrame {
ResizableDecorator label1 =
new ResizableDecorator(new JLabel(" Label1"));
ResizableDecorator button1 =
new ResizableDecorator(new JButton("Button"));
BorderDecorator label2 =
new BorderDecorator(new ResizableDecorator(
new JLabel(" doubly decorated")
));
public Frame2() {
try {
jbInit();
}
catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Frame2 frame = new Frame2();
frame.setBounds(0, 0, 200, 200);
frame.setVisible(true);
}
private void jbInit() throws Exception {
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.getContentPane().setLayout(null);
label1.setBounds(new Rectangle(10, 10, 120, 25));
label2.setBounds(new Rectangle(10, 60, 120, 25));
button1.setBounds(new Rectangle(10, 110, 120, 25));
this.getContentPane().add(label1, null);
this.getContentPane().add(label2, null);
this.getContentPane().add(button1, null);
}
}
Remarquez que vous pouvez décorer un JComponent en utilisant un ou plusieurs décorateurs, tout comme JLabel label2 du Listing 5.
Aussi, si vous devez travailler avec de nombreux décorateurs, vous pouvez envisager d’avoir une classe abstraite Decorator de laquelle toutes les classes décorateurs dérivent.
De cette manière, vous pouvez placer les méthodes communes dans la classe abstraite.
Conclusion
Cette article a fait la comparaison entre l’utilisation du sous-classement et du pattern Décorateur, en donnant deux exemples de décorateurs avec Swing.
Bien que l’utilisation du pattern Décorateur est très fréquente lorsqu’il s’agit de modifier l’apparence d’un composant Swing, son utilisation ne se limite pas à la modification de l’interface utilisateur.

Textes originaux en anglais sur O’Reilly :
target=”_blank”>Using the Decorator Pattern par Budi Kurniawan
Chargement
Commentaires récents