Aller au menu - Aller au contenu

Icône Un véritable assemblage : le pattern composite

Mise à jour : 12/02/2010
Difficulté : Intermédiaire Intermédiaire Creative Commons BY-NC-SA
96 702 visites depuis 7 jours, dont 519 sur ce chapitre classé 4/786
Comme je vous l'ai dit à la fin du précédent chapitre, la façon dont Java gère les composants de vos IHM est particulier et c'est le meilleur exemple d'utilisation du pattern composite que je puisse vous donner !

Le but principal de ce pattern est de définir une structure hiérarchique pour un ensemble de données et ceci de façon transparente pour l'utilisateur !
Je me doute bien que ce baratin ne vous parle pas trop... Mais vous allez comprendre !
Sommaire du chapitre :
Icône du chapitre
Chapitre précédent Sommaire Chapitre suivant

Creusez-vous les méninges

Comme d'habitude, nous allons avoir un petit cas pratique.
Le problème est simple, nous allons voir comment décortiquer la hiérarchie d'un forum avec ce pattern !
Vous avez un forum qui est composé :
  • de plusieurs thèmes (au choix) ;
  • chaque thème sera composé de plusieurs salons ;
  • chaque salon sera composé d'un ou plusieurs topics ;
  • et enfin, chaque topic aura un ou plusieurs messages.


Le but du jeu est de réussir à créer un ensemble d'objets interagissant ensemble et que, lorsque vous invoquez la méthode afficher de l'objet Forum, celle-ci affiche tout le contenu de votre forum !
Vous pouvez ajouter des éléments à d'autres avec une méthode add() et, si vous voyez d'autres fonctionnalités à implémenter, allez-y.

Je ne vous donne pas de solution, mais voici les classes que je vais utiliser :

Image utilisateur


La hiérarchie des ces objets est à définir par vous et vous seuls !

Non je rigole, vous pouvez vous y mettre à plusieurs. :p

Vous pouvez donc faire ce que vous voulez avec ces objets :
  • créer un objet mère ;
  • faire de l'héritage ;
  • tout changer ;
  • tout créer.


Seul le nom des classes doit être respecté. De même, il doit y avoir une cohérence entre vos objets... Par là, entendez que les Salon ne contiendront pas de Theme... Ceci est un exemple de cohérence mais il y a en a d'autres...

Comment feriez-vous pour gérer ce genre de hiérarchie d'objets ?



Réfléchissez à une hiérarchie de classes qui vous permette de faire tout ça !
Ne vous ruez pas tout de suite vers la solution, essayez par vous-mêmes... ^^

La solution : le pattern composite

Vous pourrez peut-être trouver cette définition du pattern composite :
Citation : Someone
Le pattern Composite compose des objets en arborescences.
Ceci permet de représenter une ou plusieurs hiérarchies d'objets sans se soucier de savoir si un objet est composé ou non d'autres objets.


Pour faire court, ce pattern permet de gérer très facilement des données en les structurant en hiérarchie. Voyez ça comme un JTree :

Image utilisateur


En fait, vous pouvez avoir un objet composé d'un ou plusieurs éléments qui peuvent, eux aussi, être composés d'autres éléments, etc.

Afin de simplifier la chose, imaginez une voiture. Cette dernière est composée d'un habitacle, d'un moteur, d'un bas de caisse.
Mais ces éléments sont, eux aussi, composés d'autre composants. Ainsi, nous pouvons voir que :
  • le moteur est composé d'un radiateur, d'injecteurs, d'un bloc moteur. Mais le bloc moteur est lui aussi composé de telles pièces et de telles autres...
  • le radiateur est composé de telles et telles pièces aussi...
  • les injecteurs sont des pièces... Ils ne sont pas composés !

On peut aller très loin dans ce type de hiérarchie...

Vous voyez mieux, j'espère !

Si nous en revenons à notre forum, nous pouvez voir que la structure en grappe convient très bien. Il n'y aura pas de branches mixtes, par là entendez : des branches ayant à la fois des messages et des salons (par exemple).

Avant de vous fournir un exemple de code source, voici le diagramme de classe que j'ai fait pour vous :

Image utilisateur


On le trouve bizarre, ton diagramme !

Je sais, les diagrammes de classes peuvent sembler indigestes... ^^
Par contre, vu que vous commencez à savoir les lire, vous devriez voir que :
  • vous avez une super-classe dont dérivent toutes les autres et dans laquelle la méthode toString() est définie ;
  • une classe Message qui hérite directement de cette super-classe ;
  • une autre classe abstraite (eh oui, la première super-classe est aussi abstraite), qui hérite de la super-classe, dans laquelle les méthodes add() , remove() et getChilds() sont définies ;
  • et enfin les sous-classes concrètes héritant de la classe mentionnée plus haut !


La sous-classe ComposantForum a une variable d'instance tab qui va contenir les composants de chaque objet !
La méthode toString() est redéfinie au premier niveau d'héritage.

Voici le diagramme de classe que j'ai réellement utilisé, ceci dû à certaines restrictions sur certains types d'objets :

Image utilisateur


Pourquoi avoir créé les classes Theme et Salon ?

Tout simplement car les objets de ces deux types auront des restrictions concernant le genre d'objets dont ils sont constitués...
C'est dans ces classes que nous allons pouvoir gérer les composants de nos objets ! Ainsi, nous pourrons seulement ajouter des topics dans un salon, des salons dans un thème, des thèmes dans un forum...
Vous comprenez mieux, j'espère ! ^^

Voici les codes source des classes que j'ai créées (j'ai essayé de les regrouper du haut de la hiérarchie vers le bas).

Classe Composant.java



Code : Java
1
2
3
4
5
6
7
package com.sdz.composite;

public abstract class Composant {
        //Cette variable sert seulement pour mon exemple
        protected String tabulation = "";
        public abstract String toString();        
}


Classe Message.java



Code : Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package com.sdz.composite;

public class Message extends Composant{

        private String message;
        
        public Message(String message){
                this.message = (message.trim().equals("") || message == null) ? "" : message;
                this.tabulation = "\t\t";
        }
        
        public String toString() {
                return this.tabulation + this.message;
        }
}


Classe ComposantForum.java



Code : Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.sdz.composite;

import java.util.ArrayList;

abstract class ComposantForum extends Composant{
        protected ArrayList tab = new ArrayList();
        protected String name = "";
        
        
        public ComposantForum(){}
        public ComposantForum(String nom){this.name = nom;}
        
        public void add(Composant comp){
                this.tab.add(comp);
        }
        public void remove(int i){
                this.tab.remove(i);
        }
        public ArrayList getChilds(){
                return this.tab;
        }
        public String toString(){
                String str = "";
                str += this.tabulation +"+ " + name + "\n";
                for(Object comp : this.tab){
                        str += this.tabulation + ((Composant)comp).toString() + "\n"; 
                }
                return  str;
        }
}


Classes Forum.java et Topic.java



Code : Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package com.sdz.composite;

import java.util.ArrayList;
public class Forum extends ComposantForum{

        public Forum(){
                this.name = "Forum";
        }
        public Forum(String nom) {
                super(nom);
        }

        public void add(Composant comp){
                if((comp instanceof Theme))
                        this.tab.add(comp);
        }
}



Code : Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
package com.sdz.composite;

public class Topic extends ComposantForum {        
        
        public Topic(String string) {
                super(string);
                this.tabulation += "\t\t";
        }

        public void add(Composant comp){
                if((comp instanceof Message))
                        this.tab.add(comp);
        }
}




Classe Salon.java et Theme.java



Code : Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
package com.sdz.composite;

public class Salon extends ComposantForum{
        public Salon(String nom) {
                super(nom);
                this.tabulation += "\t";
        }

        public void add(Composant comp){
                if((comp instanceof Topic))
                        this.tab.add(comp);
        }
        
}


Code : Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
package com.sdz.composite;

public class Theme extends ComposantForum{

        public Theme(String nom) {
                super(nom);
                this.tabulation += "\t"; 
        }

        public void add(Composant comp){
                if((comp instanceof Salon))
                        this.tab.add(comp);
        }
}


Classes Programmation.java et SiteWeb.java



Code : Java
1
2
3
4
5
6
7
8
package com.sdz.composite;

public class Programmation extends Theme{

	public Programmation(String nom) {
		super(nom);
	}	
}


Code : Java
1
2
3
4
5
6
7
8
package com.sdz.composite;

public class SiteWeb extends Theme {

	public SiteWeb(String nom) {
		super(nom);
	}
}


Classes CPlus.java, Java.java, Html.java, PHP.java...



Code : Java
1
2
3
4
5
6
7
8
9
package com.sdz.composite;

public class Cplus extends Salon{

        public Cplus(String nom) {
                super(nom);
        }
        
}


Code : Java
1
2
3
4
5
6
7
8
package com.sdz.composite;

public class Html extends Salon {

        public Html(String nom) {
                super(nom);
        }
}


Code : Java
1
2
3
4
5
6
7
package com.sdz.composite;

public class Java extends Salon {
        public Java(String nom) {
                super(nom);;
        }
}


Code : Java
1
2
3
4
5
6
7
8
package com.sdz.composite;

public class Php extends Salon {

        public Php(String nom) {
                super(nom);
        }
}


Code : Java
1
2
3
4
5
6
7
8
package com.sdz.composite;

public class C extends Salon {

        public C(String nom) {
                super(nom);
        }
}



Pfiou !... Il y en a, des classes... o_O
Même si vous aviez bien compris comment les objets allaient se comporter entre eux, vous devez mieux comprendre avec du code sous les yeux ! ^^
Maintenant, vous comprenez comment on gère l'interdiction de certains composants dans d'autres...
Dans notre cas, nous n'avons rien fait de spécial !
Mais rien ne vous empêche de faire une levée d'exception si l'utilisateur essaie d'ajouter un type d'objet prohibé !


Vu que nous avons toutes nos classes, il est grand-temps de voir comment celles-ci se comportent...
Voici un code qui permet de tester notre hiérarchie :

Code : Java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
//J'ai déclaré mes classes dans un autre package, d'où l'import
import com.sdz.composite.*;
public class Test {

          public static void main(String[] args) {
                  //Création de l'objet Forum
                  Forum fofo = new Forum();
                                    
                  //Création d'un thème
                  Theme prog = new Programmation("Thème -> Programmation <-");
                  //Ajoute du thème dans le forum
                  fofo.add(prog);
                  //Création d'un Salon
                  Salon java = new Java("Salon : Java");
                  //ce salon ne s'ajoutera pas : C'EST INTERDIT
                  fofo.add(java);
                  //Le salon n'aura pas non plus le thème dans la collection 
                  java.add(prog);
                  //Par contre le thème aura le salon, et vu que le forum à le thème...
                  prog.add(java);
                  
                  //On crée deux topic
                  Topic topic = new Topic("Je vous passe le bonjour !");
                  Topic topic2 = new Topic("Problème de pattern !");
                  //On les ajoute dans le salon
                  java.add(topic);
                  java.add(topic2);
                  
                  //On ajoute les message dans les topics
                  topic.add(new Message("Coucou de cysboy !"));
                  topic.add(new Message("Coucou du SDZ ! ! !"));
                  topic.add(new Message("Vous suivez toujours..."));
                  
                  topic2.add(new Message("Je ne comprends pas bien le pattern composite !"));
                  topic2.add(new Message("Même avec cet exemple tu ne comprends pas ?"));
                  topic2.add(new Message("C'est vrai que là... On comprend beaucoup mieux"));
                  
                  //On crée un deuxième salon
                  Salon c = new Cplus("Salon : C++");
                  //On l'ajoute dans programmation
                  prog.add(c);
                  //On crée aussi un topic et on l'ajoute dans le salon
                  Topic topic3 = new Topic("Problème de pointeur...");
                  c.add(topic3);
                  //On lui ajoute des messages
                  topic3.add(new Message("Je pige rien aux pointeurs... HELP !"));
                  topic3.add(new Message("Va faire un tour sur le tuto de M@teo..."));
                  topic3.add(new Message("Ah oui !.. :p"));
                  
                  //Le forum a donc : 
                  //         - un thème
                  //        - deux salons
                  //                * Java : 2 topics
                  //                * C++ : 1 topic
                  System.out.println(fofo.toString());
                  
          }        
}


Voilà ce que me donne ce code :

Image utilisateur


Simple, isn't it ?
Vous avez une belle hiérarchie d'objets qui s'entremêlent : un objet Forum, auquel vous ajoutez des thèmes ; à ces thèmes, vous ajoutez des salons qui, eux, ont des topics composés de messages.
Et lorsque vous demandez au forum de s'afficher, tout est automatique ! Même si vous passez des objets non autorisés, cela n'empêche pas le code de fonctionner ! :magicien:

Afin de mieux comprendre ce pattern, voici à quoi il ressemble dans Java.


Le composite et Java

Je vous l'ai dit au début de ce chapitre et à la fin du précédent : le pattern composite est utilisé pour gérer le contenu des fenêtres en Java.

Les objets graphiques que vous utilisez sont assemblés grâce à ce pattern, vous pouvez ainsi mettre des JPanel dans des JPanel, qui ont des JButton, le tout dans un JFrame !

Vous avez remarqué que ces composants avaient aussi une méthode add(Component comp) qui permet d'ajouter un composant !
En fait, vous pouvez voir que s'il y a ce genre de méthode dans le langage, il y a fort à parier que le composite ne soit pas loin... ^^
Tout du moins pour ce qui est des composants graphiques !
Les collections n'implémentent pas vraiment ce pattern, donc pas d'amalgame !


Pour les plus curieux d'entre vous, voici à quoi ressemble le diagramme de classes des composants Java :

Image utilisateur


Maintenant que le décryptage de diagramme de classes n'a plus de secret pour vous, vous pouvez voir clairement le pattern composite au niveau des objets Container et JComponent.

Ceux-ci acceptent donc des objets de type Component afin de les composer.
De ce fait, on peut donc ajouter un JPanel dans une JFrame, etc.

J'espère que c'est assez clair pour vous !
Très clair ! Par contre, on trouve que les patterns composite et decorator se ressemblent beaucoup !

En effet, mais ne vous y trompez pas ! :-°
Le pattern decorator implémente une relation de un à un entre ses acteurs, tandis que le pattern composite implémente une relation de un à plusieurs !
Pour faire simple, pour décorer un objet A, on l'englobe dans un autre objet B.
Pour composer un objet A, on stocke un ou plusieurs objets B dans l'objet A.

De plus, leur rôle est totalement différent :
  • le composite permet de créer des objets composés d'autres objets ;
  • le decorator permet de rajouter des fonctionnalités à un objet.


Les diagrammes de classes se ressemblent beaucoup ! Seule la flèche de composition diffère.
Oui, la flèche avec le losange rempli s'appelle une flèche de "composition".
Vous voyez que le nom de ce pattern peut être tiré de là... (Bon, là, c'est du folklore informatique...).

Je crois qu'il est grand temps de faire un tour du côté de notre topo. ;)

Ce qu'il faut retenir

  • Le pattern composite permet de faciliter la composition d'objets.
  • Ce pattern met en oeuvre une relation d'un à plusieurs envers ses différents acteurs.
  • Le composite est utilisé dans le langage Java pour gérer les composants des fenêtres graphiques.
  • Ce dernier ressemble au pattern decorator mais la relation de un à plusieurs est absente.
  • Le but du composite est de pouvoir construire des objets avec d'autres, tandis que celui du decorator est de rajouter des fonctionnalités à un objet.
J'espère que vous comprenez mieux comment fonctionne vos IHM, maintenant ! ;)
Normalement, vous devriez avoir de bonnes idées pour créer des collections faciles à utiliser...
C'est vrai que ce pattern est souvent utilisé avec des collections : normal, il y a une relation de un à plusieurs ! ^^
Chapitre précédent Sommaire Chapitre suivant

Partager

2 commentaires pour "Un véritable assemblage : le pattern composite"
Note moyenne : 3.57 / 4 (1025 votes)
Pseudo Commentaire
Hors ligne Kazou_ # Posté le 15/02/2009 à 13:48:51

Bonjour,
Tout d'abord bravo pour ce tutoriel qui permet à un débutant d'être initié à ce pattern. Cependant il comporte pas mal d'erreur conceptuelles (navrant pour un article traitant de conception applicative).

Je m'explique donc :
Premièrement il est indispensable de bien comprendre que la notion de base dans la conception applicative est qu'il faut abstraire le maximum de type que l'on manipule afin de garantir une interchangeabilité des instances. Pour ce faire nous passons souvent par des interfaces proposant une signature suffisement suffisante pour utiliser les types l'implémentant.
Une première erreur flagrante est la place des opérations ajouter et supprimer. Il n'est pas judicieux de placer ces deux opérations dans le composite (même si cette implémentation reste en accord avec les objectifs de ce pattern). Pourquoi ? Tout simplement car il met en péril l'efficacité de l'abstraction de ton modèle. Si l'on veux créer instancier un nouveaux sous type de composite et lui ajouter de nouveaux composants cela sera possible. Si maintenant nous récupérons des instances stockées dans le composite (propablement sous forme de Composant), ces méthodes ne seront alors plus accesibles car elles ne font pas partie de Composant. Il sera alors indispensable de réaliser un cast ce qui premièrement est moche, et secondement impliquer d'effectuer des tests afin de caster vers le bon type (le retour des gros switch case, inadmissible dans une bonne conception).
Oui mais on va me dire que ces deux opérations n'ont rien à faire dans le type Element ! C'est exact, cependant c'est le seul moyen de garantir une indépendance des traitements vis à vis du code client. pour résoudre le problème de ces méthodes présentes dans Elements (ce qui devient incorrect) nous avons deux solutions. La première est d'implémenter ces méthodes sans traitement, ce qui peut provoquer des difficultés de résolutions de bug. La seconde, qui est bien plus robuste, conssite à soulever une exception dans l'appel de ces méthodes dans les Elements.
D'ailleurs on voit une faiblesse dans ton exemple quand tu fais :
Code : Java
1
2
3
Forum fofo = new Forum();
// puis plus après
fofo.add(prog);

Ce qui aurait été connecte serait :
Code : Java
1
2
3
Composant fofo = new Forum();
// puis plus après
fofo.add(prog); // add ne fait pas partie de Composant ...



Le second problème réside au niveau de de Composant, il est porteur de données (ce qui l'empeche d'etre une interface). Ce n'est pas très genant, mais si un de tes composant n'a pas à posséder de tabulations, alors tu remettras en cause tout ton modèle, et bricoleras quelque chose qui marchera mais deviendra de plus en plus bancal avec le temps. Le composant ne doit etre ni plus ni moins que la signature suffisante a l'utilisation de tes composants !

Un troisème manque de rigueur se situe au niveau de l'utilisation de tes ArrayList. Tu ne précise pas de type de contenu ce qui met en grand péril la robustesse de ton implémentation.
Par defaut Java applique le type Object sur les generics.
Code : Java
1
2
3
4
5
ArrayList list = new ArrayList();
// est strictement équivalent à
ArrayList<Object> listObject = new ArrayList<Object>();
// alors que l'on devrait faire
ArrayList<Composant> listComposant = new ArrayList<Composant>();

Cette implémentation permet d'ajouter des Integer par exemple, ce qui provoquera un inévitable bug à ton cast (celui dont j'ai parlé précédement) car si tous tes Composant sont des Object, l'inverse n'est pas vrai.
On va me répondre que ce n'est pas possible de mettre un autre type que Composant car il est filtré par le type de la méthode d'ajout. C'est vrai ... Seulement le but d'un programme (et d'autant plus celui d'un DP) est d'etre réutilisé dans de multiples circonstances. Ces dernières peuvent nécéssiter de créer un nouveau composite qui fera peut-etre l'erreur de stocker un Object dans ton ArrayList ce qui provoquera un lamentable bug de ton implémentation.

Pour finir d'étailler mon argumentation, je tiens à préciser que les pratiques que j'ai énoncés sont pour la plupart des recommandations directement tirés de Design Patterns, catalogue de modèles de conception réutilisables.

http://alain-defrance.developpez.com
(Préparation) Sun Certified Mobile Application Developer, Micro Edition 1
Sun Certified Business Component Developer, Enterprise 5
Sun Certified Web Component Developer for Java 2 Platform, Enterprise 5
Sun Certified Programmer for Java 2 Platform, Standard Edition 6.0
Oracle SQL Certification (1Z0-007)
Mandriva Certification for networking Administrators
 
Hors ligne 78uzg76fq # Posté le 03/06/2010 à 17:09:54
Avatar

Super tuto, qui explique un pattern important de java.

Voir tous les commentaires