-
Notifications
You must be signed in to change notification settings - Fork 3
/
GRASP-GoF.qmd
377 lines (264 loc) · 19.4 KB
/
GRASP-GoF.qmd
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
# Décortiquer les patterns GoF avec GRASP {#sec-GRASP-GoF}
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.
## Exemple avec Adaptateur
Le pattern Adaptateur a été proposé par @GoF1994, et sa structure est illustrée sur la @fig-AdapterGoF.
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.
::: {.callout-note}
### Qu'est-ce qu'un calculateur de taxes?
On rappelle ici le contexte du problème auquel a été appliqué le pattern Adaptateur, soit le problème de calcul des taxes.
Le logiciel NextGen POS doit calculer les taxes pour les ventes, mais la compagnie vise un marché large (par exemple, les États-unis et le Canada).
Puisque le calcul des taxes peut être différent selon l'État et même la ville (par exemple [les villes de la Californie](https://www.cdtfa.ca.gov/taxes-and-fees/rates.aspx)), la compagnie qui développe NextGen POS ne veut pas maintenir toute cette information dans une partie du logiciel, car la maintenance ne sera pas rentable.
La solution est de permettre au système d'exploiter un module externe qui est un "calculateur de taxes", car ce genre de module existe (voir [TaxCloud](https://taxcloud.com/home)).
Puisque la clientèle du NextGen POS pourrait utiliser des calculateurs différents (selon sa région ou son budget), il faut constater que l'interface API de chaque calculateur sera *différente* (faute de norme sur les interfaces API de calculateurs de taxe) et *immuable* (on ne peut pas s'attendre à ce que le fournisseur de chaque calculateur change son interface API ou fournisse le code source).
Alors, le pattern Adaptateur est proposé comme solution à ce problème.
@craig_uml_2005 propose aussi que les modules externes soient considérés pour gérer l'inventaire et pour suivre la comptabilité, car ces fonctionnalités sont complexes et difficiles à faire correctement.
Encore, si NextGen POS supporte plusieurs choix (fournisseurs) de ces modules externes, il y aura ensuite le problème des interfaces API différentes et immuables. Le pattern Adaptateur sera encore utile dans ce contexte.
:::
Le chapitre A26/F23\ {{< fa solid book >}}\ présente l'exemple du pattern Adaptateur pour les calculateurs de taxes (figure F23.1/A26.1\ {{< fa solid book >}}).
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 @fig-Fig23_1.
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.).
```{.plantuml genurl="true" #fig-AdapterGoF caption="Le diagramme de classes du pattern Adaptateur [@GoF1994]."}
@startuml
!include normal.pumlinclude
hide empty members
interface Target {
{abstract} Request()
}
class Adapter implements Target {
Request()
}
note right of Adapter::Request
adaptee -> SpecificRequest()
end note
class Adaptee {
SpecificRequest()
}
Adaptee <-u- Adapter : adaptee
class Client {
}
Client -r-> Target
@enduml
```
```{.plantuml genurl="true" #fig-Fig23_1 caption="Le pattern Adaptateur appliqué pour les services externes de calculateurs de taxes. Chaque service externe a une interface API distincte et immuable. Les adaptateurs concrets permettent de normaliser l'accès à ces interfaces API à travers une interface `IAdaptateurCalculTaxes`."}
@startuml
!include normal.pumlinclude
interface IAdaptateurCalculTaxes << interface >>
class AdaptateurTaxMaster implements IAdaptateurCalculTaxes
class AdaptateurGoodAsGoldTaxPro implements IAdaptateurCalculTaxes
IAdaptateurCalculTaxes : getTaxes( Vente ) : Liste de LigneTaxes
AdaptateurTaxMaster : getTaxes( Vente ) : Liste de LigneTaxes
AdaptateurGoodAsGoldTaxPro : getTaxes( Vente ) : Liste de LigneTaxes
class TaxMaster {
calculateTaxes ( ? )
}
class GoodAsGoldTaxPro {
computeTaxes ( ? )
}
AdaptateurTaxMaster -d-> TaxMaster
AdaptateurGoodAsGoldTaxPro -d-> GoodAsGoldTaxPro
@enduml
```
## 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 @craig_uml_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:
```Java
/* 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 @fig-Fig23_3 démontre la relation entre ces principes dans le cas d'Adaptateur.
```{.plantuml genurl="true" #fig-Fig23_3 caption="Adaptateur et principes GRASP (figure F23.3/A26.3\ {{< fa solid book >}})."}
@startuml Adaptateur_GRASP
!include normal.pumlinclude
hide empty members
left to right direction
skinparam rectangle {
fontsize 0
}
rectangle " " {
class "**Adaptateur**" as A
note as GoF
**Patterns**
**GoF**
end note
}
rectangle " " {
class "Mécanisme de\n**Protection des**\n**variations**" as PV
class "Mécanisme de\n**Faible Couplage**" as FCoup
class "Mécanisme de\n**Forte Cohésion**" as FCoh
class "Exemple de\n**Polymorphisme**" as Poly
class "Mécanisme\nd'**Indirection**" as Indir
class "**Fabrication**\n**Pure**" as FP
note as GRASP
**Principes**
**GRASP**
end note
}
FCoup -down-|> PV
Poly -down-|> FCoup
Indir -down-|> FCoup
FP -down-|> FCoup
FP -down-|> FCoh
A -down-|> Indir
A -down-|> FP
A -down-|> Poly
legend
Faible Couplage est une façon d'obtenir une protection à un point de variation.
Polymorphisme est une façon d'obtenir une protection à un point de variation
et un faible couplage.
Une Indirection est une façon d'obtenir un faible couplage.
Le pattern Adaptateur est une sorte d'Indirection et de Fabrication Pure qui
applique le principe de Polymorphisme.
end legend
@enduml
```
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?
## Identifier les GRASP dans les GoF {#sec-GRASP_Adaptateur}
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 @fig-Fig23_1 du pattern Adaptateur pour les sections suivantes.
### Polymorphisme
Selon @craig_uml_2005:
::: {.callout}
**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()`.
Cet aspect du pattern est illustré sur la @fig-AdaptateurPolymorphismeNextGen.
![Le mécanisme de Polymorphisme dans le pattern Adaptateur appliqué au problème de calculateurs de taxes. `getTaxes(...)` est un appel polymorphe. Selon la configuration du système (l'objet auquel `IAdaptateurCalculTaxes` fait référence), le comportement sera différent.](images/AdaptateurCalcTaxesPolymorphism.drawio.svg){#fig-AdaptateurPolymorphismeNextGen width="60%"}
### Fabrication Pure
Selon @craig_uml_2005:
::: {.callout}
**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.
### Indirection
Selon @craig_uml_2005:
::: {.callout}
**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 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.
### Protection des variations
Selon @craig_uml_2005:
::: {.callout}
**Problème:** Comment affecter les responsabilités aux objets, aux sous-systèmes et aux 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 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).
## GRASP et réusinage
Il y a des liens entre les GRASP et les activités de [Réusinage (Refactorisation)](#sec-Refactoring).
Alors, un IDE qui automatise les réusinages peut vous aider à appliquer certains GRASP.
- GRASP Polymorphisme est relié à [_Replace Type Code with Subclasses_](https://refactoring.com/catalog/replaceTypeCodeWithSubclasses.html) et à [_Replace Conditional with Polymorphism_](https://refactoring.com/catalog/replaceConditionalWithPolymorphism.html) -- 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_](https://refactoring.com/catalog/extractClass.html).
- GRASP Indirection est relié à [_Extract Function_](https://refactoring.com/catalog/extractFunction.html) et à [_Move Function_](https://refactoring.com/catalog/moveFunction.html).
## Exercices
Pour ces exercices, suivez le modèle pour décortiquer le pattern Adaptateur, illustré à la @fig-Fig23_3.
Une bonne ressource pour les patterns GoF est la suivante:
[https://fuhrmanator.github.io/oodp-horstmann/htm/index\_fr\_en.html](https://fuhrmanator.github.io/oodp-horstmann/htm/index_fr_en.html)
::: {.callout-tip}
Il se peut que certains principes GRASP ne s'appliquent pas à un pattern GoF!
:::
::: {#exr-GRASP_iterateur}
### Itérateur
Identifiez les 4 principes GRASP dans le pattern
[Itérateur](https://fuhrmanator.github.io/oodp-horstmann/htm/Ch5/Ch5_fr.html#:~:text=Patron-,iterateur,-Contexte),
selon les directives présentées à la @sec-GRASP_Adaptateur.
:::
::: {#exr-GRASP_observateur}
### Observateur
Identifiez les 4 principes GRASP dans le pattern
[Observateur](https://fuhrmanator.github.io/oodp-horstmann/htm/Ch5/Ch5_fr.html#:~:text=Contr%C3%B4leur-,patron%20observateur,-Mod%C3%A8le),
selon les directives présentées à la @sec-GRASP_Adaptateur.
:::
::: {#exr-GRASP_strategie}
### Stratégie
Identifiez les 4 principes GRASP dans le pattern
[Stratégie](https://fuhrmanator.github.io/oodp-horstmann/htm/Ch5/Ch5_fr.html#:~:text=page-,patron%20strategie,-Strat%C3%A9gie),
selon les directives présentées à la @sec-GRASP_Adaptateur.
:::
::: {#exr-GRASP_composite}
### Composite
Identifiez les 4 principes GRASP dans le pattern
[Composite](https://fuhrmanator.github.io/oodp-horstmann/htm/Ch5/Ch5_fr.html#:~:text=composants-,patron%20composite,-Contexte),
selon les directives présentées à la @sec-GRASP_Adaptateur.
:::
::: {#exr-GRASP_decorateur}
### Décorateur
Identifiez les 4 principes GRASP dans le pattern [Décorateur](https://fuhrmanator.github.io/oodp-horstmann/htm/Ch5/Ch5_fr.html#:~:text=d%C3%A9filement%20(Scroll%20Bars)-,Patron%20D%C3%A9corateur,Contexte),
selon les directives présentées à la @sec-GRASP_Adaptateur.
:::
::: {#exr-GRASP_methode_template}
### Méthode Template
Identifiez les 4 principes GRASP dans le pattern [Méthode Template](https://fuhrmanator.github.io/oodp-horstmann/htm/Ch6/Ch6_fr.html#:~:text=java-,patron%20template%20method,-Contexte),
selon les directives présentées à la @sec-GRASP_Adaptateur.
:::
::: {#exr-GRASP_commande}
### Commande
Identifiez les 4 principes GRASP dans le pattern [Commande](https://fuhrmanator.github.io/oodp-horstmann/htm/Ch10/Ch10_fr.html#:~:text=java-,le%20patron%20command,-Contexte), selon les directives présentées à la @sec-GRASP_Adaptateur.
:::
::: {#exr-GRASP_methode_fabrique}
### Méthode Fabrique
Identifiez les 4 principes GRASP dans le pattern [Méthode de fabrique](https://fuhrmanator.github.io/oodp-horstmann/htm/Ch10/Ch10_fr.html#:~:text=methodes%20de%20fabrique%20(factory%20methods)), selon les directives présentées à la @sec-GRASP_Adaptateur.
:::
::: {#exr-GRASP_proxy}
### Proxy
Identifiez les 4 principes GRASP dans le pattern [Proxy](https://fuhrmanator.github.io/oodp-horstmann/htm/Ch10/Ch10_fr.html#:~:text=de%20m%C3%A9thode%20distante-,Le%20patron%20PROXY,Contexte,-Une%20classe), selon les directives présentées à la @sec-GRASP_Adaptateur.
:::
::: {#exr-GRASP_facade}
### Façade
Identifiez les 4 principes GRASP dans le pattern [Façade](https://fuhrmanator.github.io/oodp-horstmann/htm/Ch7/Ch7_fr.html#:~:text=la%20classe%20fa%C3%A7ade-,le%20pattern%20facade,-Contexte), selon les directives présentées à la @sec-GRASP_Adaptateur.
:::
::: {#exr-Adaptateur_Maps}
### 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:
- [Google Maps](https://developers.google.com/maps/documentation/directions);
- [Bing Maps](https://www.microsoft.com/en-us/maps/routing#routing);
- [Apple Maps](https://developer.apple.com/documentation/mapkitjs/).
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.
:::