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 pattern Adaptateur a été proposé par Gamma, Helm, Johnson, et Vlissides (1994), et sa structure est illustrée sur la figure 14.1. La structure de base peut être trompeuse, car elle ne montre qu’une seule classe Adaptee
qui représente l’interface API (externe) que l’on ne peut modifier. En réalité, ce pattern est plus intéressant lorsqu’on considère plusieurs cas de classe Adaptee
, chacune avec une interface API différente.
Le chapitre A26/F23 présente l’exemple du pattern Adaptateur pour les calculateurs de taxes (figure F23.1/A26.1 ). On remarque que cette figure dans le livre de Larman ne montre pas les classes adaptées, mais nous les avons ajoutées dans la figure 14.2. Nous faisons une hypothèse sur les noms des méthodes pour l’interface API des classes adaptées, car cette information n’est pas spécifiée. En plus, nous n’avons pas spécifié les arguments (c’est « ? »), car les détails ne sont pas connus.
Un autre point à mentionner dans le problème de calcul des taxes est que ça dépend de l’article. Selon l’État ou la province, certains articles sont sujets à des taxes et d’autres non. A priori, le calculateur de taxes est l’expert pour décider combien de taxes pour chaque article, mais le calculateur n’est pas l’expert des articles. C’est pour cela que l’Adaptateur a accès à l’objet Vente qui agrège des LigneArticles. L’Adaptateur de calculateur de taxes pourra ainsi accéder à chaque article de la vente pour déterminer les taxes. L’objet Vente a une visibilité de paramètre.
Dans le contexte du projet NextGen POS, Larman propose l’utilisation de ce pattern pour plusieurs dimensions où des services externes ont plusieurs possibilités (l’inventaire, la comptabilité, etc.).
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 GoF 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 l’on n’avait aucun adaptateur. À la place d’une méthode getTaxes()
envoyée par la classe Vente à l’adaptateur, on aurait l’obligation 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 GoodAsGoldTaxPro */
} else if (calculateurTaxes == "TaxMaster") {
/* série d'instructions pour interagir avec TaxMaster */
} 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 clarté 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’interface API distincte et immuable) de chaque calculateur de taxes externe, puisqu’elle communique directement (il y a du couplage) avec ces derniers. Lorsqu’une classe prend plus de responsabilités, ça porte atteinte à sa cohésion.
Le pattern Adaptateur comprend les principes GRASP Faible Couplage, Forte Cohésion, Polymorphisme, Indirection, Fabrication Pure et Protection des variations. La figure 14.3 démontre la relation entre ces principes dans le cas d’Adaptateur.
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
Avec cette identification de principes GRASP dans le pattern Adaptateur, êtes-vous en mesure d’expliquer en détail avec ce contexte concret comment Adaptateur est relié à ces principes ?
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 l’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.2 du pattern Adaptateur pour les sections suivantes.
14.3.1 Polymorphisme
Selon Larman (2005) :
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()
. Cet aspect du pattern est illustré sur la figure 14.4.
14.3.2 Fabrication Pure
Selon Larman (2005) :
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) :
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 interface 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) :
Les « variations » ou « l’instabilité » sont les calculateurs de taxes qui ne sont pas sous le contrôle des développeurs et développeuses du projet (ce sont des modules externes ayant chacun une interface API différente). Quant à l’« impact négatif sur les autres », il s’agit des modifications qu’auraient à faire les développeurs et développeuses sur la classe Vente chaque fois que l’on décide de supporter un autre calculateur de taxes (ou si l’interface 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 l’interface type IAdaptateurCalculTaxes. Cette définition isole (protège) la classe Vente des modifications (ajout de nouveaux calculateurs ou changements de leur interface 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 réusinages peut vous aider à appliquer certains GRASP.
- GRASP Polymorphisme est relié à Replace Type Code with Subclasses et à Replace Conditional with Polymorphism – attention, il vaut mieux appliquer ce dernier seulement quand il y a des instructions conditionnelles (
switch
) répétées à plusieurs endroits dans le code. - GRASP Fabrication Pure est relié à Extract Class.
- GRASP Indirection est relié à Extract Function et à Move Function.
14.5 Exercices
Pour ces exercices, suivez le modèle pour décortiquer le pattern Adaptateur, illustré à la figure 14.3.
Une bonne ressource pour les patterns GoF est la suivante :
https://fuhrmanator.github.io/oodp-horstmann/htm/index_fr_en.html
Exercice 14.1 (Itérateur) Identifiez les 4 principes GRASP dans le pattern Itérateur, selon les directives présentées à la Section 14.3.
Exercice 14.2 (Observateur) Identifiez les 4 principes GRASP dans le pattern Observateur, selon les directives présentées à la Section 14.3.
Exercice 14.3 (Stratégie) Identifiez les 4 principes GRASP dans le pattern Stratégie, selon les directives présentées à la Section 14.3.
Exercice 14.4 (Composite) Identifiez les 4 principes GRASP dans le pattern Composite, selon les directives présentées à la Section 14.3.
Exercice 14.5 (Décorateur) Identifiez les 4 principes GRASP dans le pattern Décorateur, selon les directives présentées à la Section 14.3.
Exercice 14.6 (Méthode Template) Identifiez les 4 principes GRASP dans le pattern Méthode Template, selon les directives présentées à la Section 14.3.
Exercice 14.7 (Commande) Identifiez les 4 principes GRASP dans le pattern Commande, selon les directives présentées à la Section 14.3.
Exercice 14.8 (Méthode Fabrique) Identifiez les 4 principes GRASP dans le pattern Méthode de fabrique, selon les directives présentées à la Section 14.3.
Exercice 14.9 (Proxy) Identifiez les 4 principes GRASP dans le pattern Proxy, selon les directives présentées à la Section 14.3.
Exercice 14.10 (Façade) Identifiez les 4 principes GRASP dans le pattern Façade, selon les directives présentées à la Section 14.3.
Exercice 14.11 (Adaptateur pour Maps) Proposez une mise en œuvre du pattern 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 ou l’utilisatrice 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 (GRASP Protection des variations) étant donné les variantes des services de calcul d’itinéraires. Cela peut être un diagramme de classes réalisé avec PlantUML.