14  Décortiquer les patterns GoF avec GRASP

Craig Larman a proposé les GRASP pour faciliter la compréhension des forces essentielles de la conception orientée objet. Dans ce chapitre, on examine la présence des GRASP dans les patterns GoF. C’est une excellente façon de mieux comprendre et les principes GRASP et les patterns GoF.

14.1 Exemple avec Adaptateur

Le chapitre A26/F23  présente l’exemple du pattern Adaptateur pour les calculateurs de taxes (figure 14.1 tirée du livre de Larman, figure A26.1/F23.1).

«interface»IAdaptateurCalculTaxesgetTaxes( Vente ) : Liste de LigneTaxesAdaptateurTaxMastergetTaxes( Vente ) : Liste de LigneTaxesAdaptateurGoodAsGoldTaxProgetTaxes( Vente ) : Liste de LigneTaxes

Figure 14.1: Le pattern Adaptateur.

14.2 Imaginer le code sans le pattern GoF

Chaque principe GRASP est défini avec un énoncé d’un problème de conception et avec une solution pour le résoudre. Pourtant, beaucoup d’exemples dans le livre de Larman (2005) sont des patterns déjà appliqués (et le problème initial n’est pas toujours expliqué en détail).

Alors, pour mieux comprendre l’application des patterns GoF, on doit imaginer la situation du logiciel avant l’application du pattern. Dans l’exemple avec l’adaptateur pour les calculateurs de taxes, imaginez le code si on n’avait aucun adaptateur. À la place d’une méthode getTaxes() envoyée par la classe Vente à l’adaptateur, on serait obligé de faire un branchement selon le type de calculateur de taxes externe utilisé actuellement (si l’on veut supporter plusieurs calculateurs). Donc, dans la classe Vente, il y aurait du code comme ceci :

/* calculateurTaxes est le nom du calculateur utilisé actuellement */
if(calculateurTaxes == "GoodAsGoldTaxPro") {
    /* série d'instructions pour interagir avec le calculateur */
} else if(calculateurTaxes == "TaxMaster") {
    /* série d'instructions pour interagir avec le calculateur */
} else if /* ainsi de suite pour chacun des calculateurs */
    /* ... */
}

Pour supporter un nouveau calculateur de taxes, il faudrait coder une nouvelle branche dans le bloc de if/then. Ça nuirait à la lisibilité du code, et la méthode qui contient tout ce code deviendrait de plus en plus longue. Même si l’on faisait une méthode pour encapsuler le code de chaque branche, ça ferait toujours augmenter les responsabilités de la classe Vente. Elle est responsable de connaître tous les détails (l’API distincte et immuable) de chaque calculateur de taxes externe, puisqu’elle communique directement (il y a du couplage) à ces derniers.

Le pattern Adaptateur comprend les principes GRASP Faible couplage, Forte cohésion, Polymorphisme, Indirection, Fabrication pure et Protection des variations. La figure 14.2 (tirée du livre de Larman, Figure A26.3/F23.3) démontre la relation entre ces principes dans le cas d’Adaptateur.

   AdaptateurPatternsGoFMécanisme deprotection desvariationsMécanisme defaible couplageMécanisme deforte cohésionExemple depolymorphismeMécanismed'indirectionFabricationpurePrincipesGRASPFaible couplageest une façon d'obtenir uneprotection à un point de variation. Polymorphismeest une façon d'obtenir uneprotection à un point de variationet unfaible couplage. Uneindirectionest une façon d'obtenir unfaible couplage. Le patternAdaptateurest une sorted'indirectionet uneFabrication purequiapplique le principe dePolymorphisme.

Figure 14.2: Adaptateur et principes GRASP (voir Larman, 2005, fig. A26.3/F23.3)

On peut donc voir le pattern Adaptateur comme une spécialisation de plusieurs principes GRASP :

  • Polymorphisme
  • Indirection
  • Fabrication pure
  • Faible couplage
  • Forte cohésion
  • Protection des variations

Êtes-vous en mesure d’expliquer dans ce contexte comment Adaptateur est relié à ces principes ? Pouvez-vous identifier les GRASP dans le pattern Adaptateur ?

14.3 Identifier les GRASP dans les GoF

Pour identifier les principes GRASP dans un pattern GoF comme Adaptateur, on rappelle la définition de chaque principe GRASP et on essaie d’imaginer le problème qui pourrait exister éventuellement. Ensuite, on explique comment le principe (et le pattern GoF) résout le problème.

Consultez la figure 14.1 du pattern Adaptateur pour les sections suivantes.

14.3.1 Polymorphisme

Selon Larman (2005) :

Problème : Qui est responsable quand le comportement varie selon le type ?
Solution : Lorsqu’un comportement varie selon le type (classe), affectez la responsabilité de ce comportement – avec des opérations polymorphes – aux types selon lesquels le comportement varie.

Le « comportement qui varie » est la manière d’adapter les méthodes utilisées par le calculateur de taxes choisi à la méthode getTaxes(). Alors, cette « responsabilité » est affectée au type interface IAdaptateurCalculTaxes (et à ses implémentations) dans l’opération polymorphe getTaxes().

14.3.2 Fabrication pure

Selon Larman (2005) :

Problème : En cas de situation désespérée, que faire quand vous ne voulez pas transgresser les principes de faible couplage et de forte cohésion ?
Solution : Affectez un ensemble très cohésif de responsabilités à une classe « comportementale » artificielle qui ne représente pas un concept du domaine - une entité fabriquée pour augmenter la cohésion, diminuer le couplage et faciliter la réutilisation.

La Fabrication pure est la classe « comportementale et artificielle » qui est la hiérarchie IAdaptateurCalculTaxes (comprenant chaque adaptateur concret). Elle est comportementale puisqu’elle ne fait qu’adapter des appels. Elle est artificielle puisqu’elle ne représente pas un élément dans le modèle du domaine.

L’ensemble des adaptateurs concrets ont des « responsabilités cohésives » qui sont la manière d’adapter la méthode getTaxes() aux méthodes (immuables) des calculateurs de taxes externes. Elles ne font que ça. La cohésion est augmentée aussi dans la classe Vente, qui n’a plus la responsabilité de s’adapter aux calculateurs de taxes externes. C’est le travail qui a été donné aux adaptateurs concrets.

Le couplage est diminué, car la classe Vente n’est plus couplée directement aux calculateurs de taxes externes. La réutilisation des calculateurs est facilitée, car la classe Vente ne doit plus être modifiée si l’on veut utiliser un autre calculateur externe. Il suffit de créer un adaptateur pour ce dernier.

14.3.3 Indirection

Selon Larman (2005) :

Problème : Comment affecter les responsabilités pour éviter le couplage direct ?
Solution : Pour éviter le couplage direct, affectez la responsabilité à un objet qui sert d’intermédiaire avec les autres composants ou services.

Le « couplage direct » qui est évité est le couplage entre la classe Vente et les calculateurs de taxes externes. Le pattern Adaptateur (général) cherche à découpler le Client des classes nommées Adaptee, car chaque Adaptee a une API différente pour le même genre de « service ». Alors, la responsabilité de s’adapter aux services différents est affectée à la hiérarchie de « classes intermédiaires », soit l’interface type IAdaptateurCalculTaxes et ses implémentations.

14.3.4 Protection des variations

Selon Larman (2005) :

Problème : Comment affecter les responsabilités aux objets, sous-systèmes et systèmes de sorte que les variations ou l’instabilité de ces éléments n’aient pas d’impact négatif sur les autres ?
Solution : Identifiez les points de variation ou d’instabilité prévisibles et affectez les responsabilités afin de créer une « interface » stable autour d’eux.

Les « variations ou l’instabilité » sont les calculateurs de taxes qui ne sont pas sous le contrôle des développeurs(euses) du projet (ce sont des modules externes ayant chacun une API différente). Quant à l’” impact négatif sur les autres”, il s’agit des modifications que les développeurs(euses) auraient à faire sur la classe Vente chaque fois que l’on décide de supporter un autre calculateur de taxes (ou si l’API de ce dernier évolue).

Quant aux « responsabilités » à affecter, c’est la fonctionnalité commune de tous les calculateurs de taxes, soit le calcul de taxes. Pour ce qui est de l’« interface stable », il s’agit de la méthode getTaxes(), qui ne changera (probablement) jamais. Elle est définie dans le type-interface IAdaptateurCalculTaxes. Cette définition isole (protège) la classe Vente des modifications (ajout de nouveaux calculateurs ou changements de leur API).

14.4 GRASP et réusinage

Il y a des liens entre les GRASP et les activités de Réusinage (Refactorisation). Alors, un IDE qui automatise les refactorings peut vous aider à appliquer certains GRASP.

14.5 Exercices

Pour ces exercices, suivez le modèle pour décortiquer le patron Adaptateur, illustré à la figure 14.2.

Une bonne ressource pour les patterns GoF est la suivante :

https://fuhrmanator.github.io/oodp-horstmann/htm/index_fr_en.html

Astuce

Il se peut que certains principes GRASP ne s’appliquent pas à un patron GoF !

Exercice 14.1 (Itérateur) Identifiez les 4 principes GRASP dans le patron Itérateur, selon les directives à la Section 14.3.

Exercice 14.2 (Observateur) Identifiez les 4 principes GRASP dans le patron Observateur, selon les directives à la Section 14.3.

Exercice 14.3 (Stratégie) Identifiez les 4 principes GRASP dans le patron Stratégie, selon les directives à la Section 14.3.

Exercice 14.4 (Composite) Identifiez les 4 principes GRASP dans le patron Composite, selon les directives à la Section 14.3.

Exercice 14.5 (Décorateur) Identifiez les 4 principes GRASP dans le patron Décorateur, selon les directives à la Section 14.3.

Exercice 14.6 (Méthode Template) Identifiez les 4 principes GRASP dans le patron Méthode Template, selon les directives à la Section 14.3.

Exercice 14.7 (Commande) Identifiez les 4 principes GRASP dans le patron Commande, selon les directives à la Section 14.3.

Exercice 14.8 (Méthode Fabrique) Identifiez les 4 principes GRASP dans le patron Méthode de fabrique, selon les directives à la Section 14.3.

Exercice 14.9 (Proxy) Identifiez les 4 principes GRASP dans le patron Proxy, selon les directives à la Section 14.3.

Exercice 14.10 (Façade) Identifiez les 4 principes GRASP dans le patron Façade, selon les directives à la Section 14.3.

Exercice 14.11 (Adaptateur) Proposez une mise en oeuvre du patron GoF Adaptateur pour un système de livraison qui peut être configuré avec trois variantes du service de calcul d’itinéraires :

Le système veut obtenir une liste d’étapes (des directions) pour se rendre à une destination à partir d’un point de départ. L’utilisateur du système pourra décider lequel des services lui convient dans les préférences.

Le but de l’exercice est de déterminer l’interface stable (Protection des variations GRASP) étant donné les variantes des services de calcul d’itinéraires. Cela peut être un diagramme de classe réalisé avec PlantUML.