P
'
t
i
t
e
C
h
a
t
t
e
 
spacer~ A BARTENDER IS JUST A PHARMACIST WITH A LIMITED INVENTORY Articles | Connexion
 
~Les motifs Composite et Décorateur

 Présentation

La diversité des situations couvertes par les motifs de conception décrits dans, notamment, l'ouvrage du Gang Of Four garantit au programmeurs des solutions élégantes pour la majorité des problèmes courants. Découvrons sans plus tarder ce que nous réservent les Design Patterns cette fois-ci...
 Sommaire


 Le motif Composite

Le motif de conception Composite autorise la concrétisation de structures arborescentes d'objets. Ces structures représentent des hiérarchies composant/composé. L'intérêt majeur d'une telle hiérarchie se situe au niveau de l'utilisateur du motif : la manipulation d'un objet individuel ou d'une combinaison d'objet est traitée de la même façon.

Les logiciels de dessin vectoriel constituent un exemple probant d'utilisation du motif de conception Composite. Dans ce type d'outil, chaque élément graphique, les lignes, les rectangles, et ainsi de suite, peut se voir groupé avec un ou plusieurs éléments. L'approche consistant à proposer des conteneurs et des primitives révèle rapidement ses limites. Une architecture objet optant pour cette solution nécessiterait des traitements différents selon la nature des objets manipulés par l'utilisateur.

Ainsi, le code source contiendrait de nombreux traitement de cas de type if/else ou switch/case afin de répartir les rôles.

Le design pattern Composite apporte une réponse à ce problème en proposant une interface unique pour les conteneurs et les primitives. Considérons un outil de dessin vectoriel. Le listing suivant présente une interface simplifiée destinée à caractériser les composants et les composés d'un dessin.

interface Component
{
  void paint(Graphics g);
  void add(Component c);
  void remove(Component c);
  Iterator getChilds();
}
       
      
JextCopier dans Jext | Jext | Plugin Codegeek
La structure Component définit un objet que l'on peut à loisir dessiner sur une surface graphique. En outre, ce composant peut devenir un composé lorsque des enfants lui sont ajoutés. L'implémentation de la gestion des enfants reste entièrement à la charge des classes de l'application.

Toutefois, nous pourrions spécialiser cette définition en ajoutant une nouvelle méthode destinée à connaître le statut de l'objet : composé ou composé :

boolean isComposite();
       
      
JextCopier dans Jext
Les listings suivants contiennent deux implémentations différentes de cette interface, l'une définissant un composant et l'autre un composé. Aucun des exemples ne propose la méthode isComposite(), néanmoins la classe Circle devrait l'implémenter pour renvoyer false tandis que Group retournerait true.

public class Circle implements Component
{
  private int x, y, radius;
  // ...
  public void paint(Graphics g)
  {
    g.drawOval(x, y, radius * 2, radius * 2);
  }

  public void add(Component c) { }
  public void remove(Component c) { }

  public Iterator getChilds()
  { return null; }
}
       
      
JextCopier dans Jext
public class Group implements Component
{
  private ArrayList childs;
  // ...
  public void paint(Graphics g)
  {
    Iterator i = getChilds();
    while (i.hasNext())
      ((Component) i.next()).paint(g);
  }

  public void add(Component c)
  { childs.add(c); }

  public void remove(Component c)
  { childs.remove(c); }

  public Iterator getChilds()
  { return childs.iterator(); }
}
       
      
JextCopier dans Jext
Vous remarquerez que l'utilisation de ces deux classes est rigoureusement identique. Le rôle d'un objet composite consiste à appeler la méthode de comportement de ses enfants. La méthode de comportement est ici définie par la méthode d'affichage paint(). Dans le cadre de l'organigramme d'une société, cette méthode pourrait correspondre au calcul de la somme de tous les salaires d'un département donné (en admettant qu'un département puisse contenir des sous-départements). Une hiérarchie composant/composé décrit un arbre, que les applications pourront très simplement afficher à l'écran. Ainsi, une application de dessin vectoriel sera à même de présenter la hiérarchie complète des diverses primitives du dessin aux yeux de l'utilisateur.


 Le motif Décorateur

A l'instar du motif Composite, le motif de conception Décorateur appartient à la catégorie des modèles structuraux. La finalité de ce motif concerne l'attachement dynamique de responsabilités supplémentaires à un objet. Ceci permet d'ajouter de nouvelles fonctionnalités à un objet sans faire appel à la dérivation. Prenons un exemple classique d'interface graphique : l'ajout de bordures à un composant. Le programmeur pourrait décider de dériver les divers composants de son interface, ou de les faire tout hériter d'une même classe, afin de leur ajouter une bordure. Malheureusement, cette approche se révèle très lourde et handicape fortement la structure du code source.

Une meilleure approche consiste à inclure le composant dans une enveloppe chargée de lui ajouter une bordure. L'enveloppe est alors appelée Décorateur. Ce Décorateur doit être transparent pour le composant qu'il modifie. Pour ce faire, le Décorateur transfère les requêtes (par exemple l'affichage) au composant et réalise certains opérations supplémentaires. Cette notion de transparence, à savoir que le composant n'a aucun connaissance de la présence d'un Décorateur l'enveloppant, octroie l'opportunité d'imbriquer récursivement des Décorateurs les uns par dessus les autres.

L'exemple de composants graphiques permet une découverte rapide des Décorateurs. Nous définissons donc en Java (avec l'API Swing) une interface Décorateur :

public class Decorator extends JComponent
{
  public Decorator(JComponent c)
  {
    setLayout(new BorderLayout());
    add(BorderLayout.CENTER, c);
  }
}
       
      
JextCopier dans Jext | Jext | Plugin Codegeek
Ce Décorateur n'a absolument aucune incidence sur le composant désigné en paramètre du constructeur. En étendant ce Décorateur nous pouvons créer un Décorateur rayant le contenu du composant :

public class StrikeThroughDecorator extends Decorator
{
  public void paint(Graphics g)
  {
    super.paint(g);
    g.setColor(Color.red);
    g.drawLine(0, getHeight() / 2, getWidth(), getHeight() / 2 );
  }
}
       
      
JextCopier dans Jext
Dorénavant, notre application pourra afficher un nombre illimité de composants "barrés" en rédigeant des instructions de ce genre :

add(new StrikeThroughDecorator(new JLabel("Hello")));
       
      
JextCopier dans Jext
L'interface de notre Décorateur de base dérive de la classe JComponent. Or son constructeur reçoit en paramètre une instance de JComponent. De la sorte, nous pourrions obtenir un composant barré sur lequel un calque transparent bleu serait ajouté :

add(new AlphaLayerDecorator(
      new StrikeThroughDecorator(new JLabel("Hello"))));
       
      
JextCopier dans Jext
Ces méthodes simples permettent de personnaliser rapidement et de manière souple une interface graphique. Les librairies standards du JDK recèlent quelques exemples de motifs de conception Décorateurs. L'une des implémentations les plus employées réside dans le package java.io. Ce package contient un ensemble de classes spécialiées dans la lecture et l'écriture de flux de données. La grande force de ce package réside dans sa capacité d'encapsulation des diverses classes.

Voici par exemple un extrait de code source employant un flux de lecture à tampon avec capacité de revenir en arrière :

PushbackInputStream in = new PushbackInputStream(new BufferedInputStream(
                                    new FileInputStream("fichier")));
       
      
JextCopier dans Jext
Le flux créé se décrit ainsi : nous possédons un décorateur "Pushback" appliqué à un décorateur "Buffered" lui-même appliqué à un flux de lecture de fichier. La décoration permet dans ce type de situation de coupler les traitement en, par exemple, compressant dans un fichier un flux de sortie crypté.



par Romain Guy
romain.guy@jext.org
http://www.jext.org
Dernière mise à jour : 14/10/2006



 
#ProgX©2005 Mathieu GINOD - Romain GUY - Erik LOUISE