• Aucun résultat trouvé

Chapitre 7 - L’Approche Orientée Objet 139

7.3. Composition, agrégation et association

7.3.1. Différences conceptuelles

Nous présentons ici trois types de relation similaires entre classes: la composition, l’agrégation et l’association. Les deux premières expriment l’appartenance d’un objet à un objet plus vaste. Elles traduisent l’action "pos-sède" ou "est composé(e)" entre un composé et ses composants. Par exemple, la relation "un graphe est

Bruno Bachelet CHAPITRE7 - L’APPROCHEORIENTÉEOBJET

posé d’arcs et de noeuds" se traduit par une composition entre la classeGrapheet les classesArcetNoeud (cf. figure 7.7). Dans une composition, les composants (e.g. les arcs ou les noeuds) n’ont pas d’existence en dehors de leur composé (e.g. le graphe). La vie des composants est donc liée directement à celle du composé. L’agrégation diffère de la composition sur ce point, les composants ont une vie propre et n’appartiennent à un composé que pour une durée limitée. Notamment, dans une agrégation, la destruction du composé n’entraîne pas la destruction des composants, contrairement à la composition. Par exemple, la relation "un cycle est composé d’arcs" est traduite par une agrégation entre la classeCycleet la classeArc(cf. figure 7.8).

G rap he Noeud Arc noeuds graphe arcs * *

Figure 7.7: Un exemple de composition.

Cycle

Arc

* arcs

Figure 7.8: Un exemple d’agrégation.

Noeud

Arc

* * arcsEntrants

arcsSortants

origine destination

Figure 7.9: Un exemple d’association.

L’association diffère des deux autres relations par le fait qu’il n’y ait pas de dominance de l’une des deux classes impliquées. Par exemple, la relation "un arc possède un noeud origine et un noeud destination" se traduit par deux associations entre la classe Arc et la classe Noeud(cf. figure 7.9). Pour une association entre deux classes, il est possible de spécifier combien de relations il peut effectivement exister entre des instances de ces classes, simplement en apposant une cardinalité sur l’association. Dans la figure 7.9, l’arc possède un noeud origine et un noeud destination, donc la cardinalité du point de vue de l’arc est1 pour les deux associations. En

revanche du point de vue du noeud, l’association peut aller de0 à n, d’où le symbole∗ pour symboliser cette

cardinalité. Une cardinalité explicite peut être apposée (e.g.1..4).

7.3.2. Similitudes d’implémentation

Ces trois relations, dont la différence est très importante au niveau conceptuel, sont quasiment identiques au niveau de l’implémentation. Toutes les trois sont en effet traduites par la présence d’attributs des classes com-posants dans la classe composé.

Rectangle − p1 : Point − p2 : Point ... + constructeur() + destructeur() ... Rectangle Point 2..2

Pas besoin de construire ou de détruire p1 et p2, c’est automatique.

Figure 7.10: Un exemple d’implémentation (avec attributs objets) d’une composition.

Pour commencer, tout attribut objet d’une classe représente forcément une composition entre la classe pos-sédant l’attribut et la classe indiquant le type de l’attribut. Par exemple, la figure 7.10 représente une classe

Rectanglepossédant deux attributs de la classePoint. Il s’agit d’une composition puisque les composants

(i.e. les attributs) sont créés et supprimés en même temps que le composé (i.e. un objet de la classe). Plus pré-cisément, lorsque le constructeur (respectivement le destructeur) deRectangleest appelé, les constructeurs (respectivement les destructeurs) dep1etp2sont automatiquement exécutés.

CHAPITRE7 - L’APPROCHEORIENTÉEOBJET Bruno Bachelet

Reprenons l’exemple de la composition de la figure 7.7, la classe Graphe possède deux attributs arcs et noeuds, deux listes de références respectivement d’arcs et de noeuds (cf. figure 7.11). Les attributs induits par une composition peuvent donc être des références, la classe composé doit alors gérer la création et la suppression des composants (cf. figure 7.11).

Noeud − graphe : Graphe * ... Arc − graphe : Graphe * ... G rap he − arcs[] : Arc * − noeuds[] : Noeud * ... + destructeur() + ajouterArc(...) + ajouterNoeud(...) ... ... arcs.insert(new Arc(...)); ... ... nodes.insert(new Node(...)); ... ... for i = 0 to arcs.size() do delete arcs[i]; end do; for i = 0 to nodes.size() do delete nodes[i]; end do; ...

Figure 7.11: Un exemple d’implémentation (avec attributs références) d’une composition.

L’agrégation et l’association se traduisent également par des attributs références d’objets. Dans l’exemple de l’agrégation (cf. figure 7.8), la classe Cyclepossède un attributarcs qui est une liste de références sur des objets de la classe Arc (cf. figure 7.12). Dans l’exemple d’association (cf. figure 7.9), la classe Arc possède deux attributs, origineetdestinationqui sont des références sur des objets de la classeNoeud (cf. figure 7.13). Cette dernière possède deux attributsarcsEntrantsetarcsSortantsqui sont des listes de références d’objets de la classeArc.

Cycle − arcs[] : Arc * ... + destructeur() + ajouterArc(a : Arc *) ...

Les arcs ne sont pas détruits ici.

...

arcs.insert(a); ...

Figure 7.12: Un exemple d’implémentation d’une agrégation.

Arc − origine : Noeud * − destination : Noeud * ... Noeud − arcsEntrants[] : Arc * − arcsSortants[] : Arc * ... * * arcsEntrants arcsSortants origine destination

Figure 7.13: Un exemple d’implémentation d’une association.

Ces trois relations, que nous résumerons par le terme composition dans la suite du document afin de faciliter la discussion, sont une possibilité supplémentaire de réutilisabilité. Par l’assemblage d’objets, il est possible d’en construire un plus complexe et de lui ajouter des fonctionnalités. Contrairement à l’héritage, il n’est pas possible de modifier les fonctionnalités des objets réutilisés, en revanche il est possible de les faire interagir pour produire des fonctionnalités plus complexes. Cela suppose de la part des composants fournis d’être très

Bruno Bachelet CHAPITRE7 - L’APPROCHEORIENTÉEOBJET

complets pour pouvoir être adaptés à de nombreux besoins. C’est donc le principal défaut de ce type de réutilisation: si le concepteur de l’objet réutilisé n’a pas prévu une fonctionnalité, elle ne peut pas toujours être rajoutée (e.g. si l’accès à un élément privé est nécessaire). L’avantage majeur en revanche est la réutilisation en boîte noire des classes, ce qui apporte une grande indépendance entre les composants et leur composé. Une modification de l’implémentation d’un composant n’entraîne aucune modification pour le composé, ce qui est tout à fait différent de l’héritage, qui est une réutilisation en boîte blanche (cf. [Gamm95]), et qui apporte une dépendance forte entre la super-classe et ses sous-classes.

7.3.3. Délégation

La tentation avec l’approche objet est grande d’utiliser l’héritage chaque fois que l’on souhaite étendre un composant (cf. [John88], l’approche programming-by-difference). C’est la solution de facilité. On a besoin de modifier la fonctionnalité d’une classe, il suffit d’hériter, de modifier les quelques méthodes qu’il faut et c’est terminé. Outre le nombre important de classes que cela peut produire, nous avons déjà discuté des problèmes de maintenabilité qu’entraîne l’héritage. Tout cela peut conduire à long terme à une hiérarchie d’héritage complexe où le réutilisateur se perd (cf. [Gamm95]), ce qui est dommage quand on sait que l’un des objectifs majeurs du paradigme objet est de fournir une vision claire de la structure d’un logiciel.

En outre, la hiérarchie fournie par l’héritage est statique, cela veut dire qu’au cours de l’exécution du pro-gramme, il n’est pas possible de modifier l’héritage d’une classe pour qu’un objet héritant des fonctionnalités d’une classe puisse hériter de celles d’une autre. Considérons l’exemple de la figure 7.14a qui présente une hiérarchie d’héritage avec une classe de baseFormespécialisée en deux sous-classes CarréetCercle. Une classeIcônedoit hériter des propriétés d’une des classesForme.

Icône Carré Icône Cercle Icône Carré Cercle ou

(b) Les possibilités d’héritage.

+ touché(x : entier, y : entier) : booléen

return Carré::touché(x,y);

ou

return Cercle::touché(x,y);

Forme

Carré Cercle

+ touché(x : entier, y : entier) : booléen Virtuelle.

(a) La hiérarchie des formes.

Figure 7.14: Un exemple de l’intérêt de la délégation.

Comme le montre la figure 7.14b, il est possible de décider statiquement de la forme de l’icône par un héritage

deCarré, deCercleou des deux (c’est ce que l’on appelle l’héritage multiple). Imaginons maintenant que

l’on souhaite savoir si un clic de souris a atteint un objetIcône. Il suffit alors d’appeler la méthodetouché() avec en paramètre les coordonnées de la souris. Pour l’héritage simple, la méthode héritée est appelée. Pour l’héritage multiple, il faut faire le choix d’appeler la méthode deCarréou deCercleau moment de l’écriture de la classeIcône(cf. figure 7.14b).

CHAPITRE7 - L’APPROCHEORIENTÉEOBJET Bruno Bachelet

Mais comment faire pour permettre à un tel objet de changer sa forme en cours d’exécution et bien entendu d’hériter des caractéristiques de sa nouvelle forme ? Grâce à la composition, il est possible de changer dy-namiquement la forme de l’objetIcône. Cette technique est appelée délégation (cf. [John91]). Elle consiste pour la classe qui hérite à agréger un objet de la classe dont elle veut les fonctionnalités. Elle fournit ensuite une interface identique à celle de l’objet agrégé. Chaque fois qu’une de ses méthodes est appelée, elle délègue l’appel, i.e. elle appelle la méthode homonyme de l’attribut qu’elle agrège. Dans notre exemple, il suffit que

Icôneagrège un objet de la classeForme, pouvant être changé par la méthodesetForme(). Ainsi, chaque

fois qu’elle est appelée, la méthode touché() délègue son travail à la méthode touché()de son attribut forme.

Forme

return forme.touché(x,y);

Icône

+ touché(x : entier, y : entier) : booléen + setForme(f : Forme)

− forme : Forme *

forme

forme = f;

Figure 7.15: Un exemple de délégation.

La délégation est une technique intéressante pour remplacer l’héritage. Elle fournit une forme de polymor-phisme dynamique gérée en partie par le concepteur. Cependant, elle nécessite toujours le polymorpolymor-phisme de l’héritage, ici pour la méthodetouché()de la classeForme. L’intérêt est donc principalement le dynamisme de ce pseudo-héritage. Mais il faut se méfier de la complexité (liée à l’augmentation des paramètres de la classe) qu’apporte cette technique dans l’utilisation du composant. [Gamm95] conseille donc d’employer cette technique avec modération, quand cela est réellement nécessaire (c’est le cas de notre exemple), et surtout quand cela simplifie la conception.