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).
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.
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) :
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) :
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 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(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.
- 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 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
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.