-
Notifications
You must be signed in to change notification settings - Fork 3
/
GRASP.qmd
1078 lines (857 loc) · 49.2 KB
/
GRASP.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
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# Principes GRASP
GRASP est un acronyme de l'expression anglaise "General Responsibility Assignment Software Patterns", c'est-à-dire les principes pour affecter les responsabilités logicielles dans les classes.
Une approche GRASP devrait amener un design vers la modularité et la maintenabilité.
L'acronyme d'une expression vulgarisée pourrait être POMM: "Principes pour déterminer Où Mettre une Méthode."
En tant qu'ingénieur logiciel ou ingénieure logiciel, vous devez souvent décider où placer une méthode (dans quelle classe), et cette décision ne devrait pas être prise de manière arbitraire, mais plutôt en suivant les directives d'ingénierie favorisant la modularité.
Alors, les GRASP sont les directives qui vous aident à prendre des décisions de conception, menant à un design avec moins de couplage inutile et avec des classes plus cohésives. Les classes cohésives sont plus faciles à comprendre, à maintenir et à réutiliser.
::: {.callout-tip appearance="minimal"}
{{< fa solid hat-cowboy-side >}} Avez-vous déjà une bonne expérience en programmation?
Avez-vous l'habitude de coder rapidement des solutions qui fonctionnent?
Si la réponse est oui, alors travailler avec les principes GRASP peut être un défi pour vous.
Dans la méthodologie enseignée dans ce manuel, vous devez être en mesure de justifier vos choix de conception, et cela va vous ralentir au début (réflexe du "hacking cowboy" peut-être?).
Le but avec les principes GRASP est de (ré)apprendre à faire du code qui fonctionne, mais qui est également facile à maintenir.
C'est normal au début que ça prenne plus de temps, car il faut réfléchir pour appliquer les principes.
Une fois que vous aurez l'habitude d'utiliser les GRASP, vous serez encore rapide avec votre développement, mais, en plus, votre design sera meilleur sur le plan de la maintenabilité, et vous aurez plus de confiance dans vos choix.
:::
## Spectre de la conception {#sec-SpectreDeLaConception}
Neal Ford [-@Ford2009] a proposé la notion d'effort pour la conception qu'il a nommée le "Spectre de la conception".
La @fig-SpectreConception illustre le principe.
``` {#fig-SpectreConception .plantuml genurl="true" caption="Spectre de la conception [@Ford2009]."}
@startuml
!include ecriture.pumlinclude
scale 1.2
skinparam classBorderColor hidden
skinparam classBackgroundColor hidden
skinparam classArrowColor black
hide stereotypes
hide empty members
class "<size:30>[" as hacking
class "« Hacking cowboy »" as Nh
class "**+**" as agile <<middle>>
class " Agile " as Na
class "<size:30>]" as waterfall
class "« Cascade pure »" as Ncp
hacking -right[bold]- agile
agile -right[bold]- waterfall
waterfall <-u[dashed]- Ncp
agile <-u[dashed]- Na
hacking <-d[dashed]- Nh
@enduml
```
À une extrémité, il y a la mentalité de mettre presque zéro effort pour une conception, que l'on nomme "hacking cowboy".
C'est le cas d'un hackathon (un marathon de programmation durant 24 ou 48 heures où il faut produire une solution rapidement), au cours duquel vous ne feriez pas un logiciel avec 10 patterns GoF et vous ne feriez pas non plus les diagrammes UML pour réfléchir à votre architecture.
Vous savez aussi que le code produit lors d'un hackathon ne sera pas facile à maintenir.
Le seul but de cette activité est de développer du code qui marche pour montrer une idée intéressante.
Une situation similaire s'applique à certains contextes d'entreprise, par exemple une entreprise en démarrage qui a seulement un financement pour six mois.
Si une solution de ["produit minimum viable" (MVP en anglais)](https://en.wikipedia.org/wiki/Minimum_viable_product) {{< fa brands wikipedia-w >}} n'existe pas à la fin de la période de financement, l'entreprise n'existera plus, car il n'y aura pas une seconde période de financement.
Si, par contre, l'entreprise est financée pour une seconde période, la conception du code pourrait avoir besoin de beaucoup de soin et de maintenance, car elle aura été préalablement négligée.
Cette négligence de la conception (pour la maintenabilité) est aussi nommée la [dette technique](#sec-DetteTechnique).
À l'autre extrémité du spectre de la conception, on retrouve ce que l'on nomme "Cascade pure", où beaucoup d'effort a été déployé pour la conception.
Dans le cycle de vie en cascade, on met un temps fixe (par exemple plusieurs mois) à étudier la conception.
Comme toute chose poussée à l'extrême, ce n'est pas idéal non plus. Dans son livre, @craig_uml_2005 explique en détail des problèmes posés par une approche en cascade.
En dépit des problèmes dus à l'approche en cascade, elle est encore utilisée dans certains domaines, par exemple les logiciels pour le contrôle d'avions ou le contrôle d'appareils médicaux.
La sécurité et la robustesse des logiciels sont très importantes, alors on passe beaucoup de temps à vérifier et à valider la conception.
Puisque les exigences sont plus stables (et que les développeurs et les développeuses ont *a priori* une meilleure compréhension du domaine), l'approche en cascade n'est pas si mal.
Pourtant, le coût pour produire des logiciels certifiés est énorme.
Le spectre de la conception est très important pour le monde réel, parce qu'une ingénieure ou un ingénieur devrait pouvoir s'adapter selon les attentes de son travail.
Le dogme sur "la bonne manière" de développer un logiciel est souvent sans contexte.
C'est le contexte de l'entreprise pour laquelle vous travaillez qui peut quantifier les efforts à mettre sur la conception.
Cependant, méfiez-vous des entreprises qui ne portent aucune attention à la conception (l'extrémité "hacking cowboy" du spectre), même si l'on vous dit que c'est "agile".
<!-- Si vous négligez complètement la conception, vous pouvez peut-être produire du code qui fonctionne plus vite à court terme.
Mais il faudra repayer la dette technique un jour.
Un moyen de gérer cette dette technique est de *réusiner* (anglais refactor). -->
\newpage
## Tableau des principes GRASP {#tbl-GRASPTable}
Voici un extrait du livre de @craig_uml_2005.
<!-- Attention: le tableau suivant a été généré avec l'éditeur "Visual" de RStudio -->
+----------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Pattern | Description |
+:=========================================================+:==============================================================================================================================================================================================================================================================+
| Expert en Information\ | Un principe général de conception d'objets et d'affectation des responsabilités.\newline |
| *F16.11/A17.11*\ {{< fa solid book >}} | |
| | Affectez une responsabilité à l'expert -- la classe qui possède les informations nécessaires pour s'en acquitter.\newline |
+----------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Créateur\ | Qui crée? (Notez que Fabrique Concrète est une solution de rechange courante.)\newline |
| *F16.10/A17.10*\ {{< fa solid book >}} | |
| | Affectez à la classe B la responsabilité de créer une instance de la classe A si l'une des assertions suivantes est vraie: |
| | |
| | 1. B contient A |
| | 2. B agrège A \[à favoriser\] |
| | 3. B a les données pour initialiser A |
| | 4. B enregistre A |
| | 5. B utilise étroitement A\newline |
+----------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Contrôleur\ | Quel est le premier objet en dehors de la couche présentation qui reçoit et coordonne ("contrôle") les opérations système?\newline |
| *F16.13/A17.13*\ {{< fa solid book >}} | |
| | Affectez une responsabilité à la classe qui correspond à l'une de ces définitions: |
| | |
| | 1. \[Contrôleur de façade\] [Cette classe] représente le système global, un "objet racine", un équipement ou un sous-système (contrôleur de façade). |
| | 2. \[Contrôleur de session\] [Cette classe] représente un scénario de cas d'utilisation dans lequel l'opération système se produit (*contrôleur de session* ou contrôleur de cas d'utilisation). \[On la nomme GestionnaireX, où X est le nom du cas |
| | d'utilisation.]\newline |
+----------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Faible Couplage\ | Comment minimiser les dépendances?\newline |
| (évaluation)\ | |
| *F16.12/A17.12*\ {{< fa solid book >}} | Affectez les responsabilités de sorte que le couplage (inutile) demeure faible. Employez ce principe pour évaluer les alternatives.\newline |
+----------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Forte Cohésion\ | Comment conserver les objets cohésifs, compréhensibles, gérables et, en conséquence, obtenir un Faible Couplage?\newline |
| (évaluation)\ | |
| *F16.14/A17.14*\ {{< fa solid book >}} | Affectez les responsabilités de sorte que les classes demeurent cohésives. Employez ce principe pour évaluer les différentes solutions.\newline |
+----------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Polymorphisme\ | Qui est responsable quand le comportement varie selon le type?\newline |
| *F22.1/A25.1*\ {{< fa solid book >}} | |
| | 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.\newline |
+----------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Fabrication Pure *F22.2/A25.2*\ {{< fa solid book >}} | 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?\newline |
| | |
| | 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.\newline |
+----------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Indirection\ | Comment affecter les responsabilités pour éviter le couplage direct?\newline |
| *F22.3/A25.3*\ {{< fa solid book >}} | |
| | Affectez la responsabilité à un objet qui sert d'intermédiaire avec les autres composants ou services.\newline |
+----------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Protection des Variations\ | 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?\newline |
| *F22.4/A25.4*\ {{< fa solid book >}} | |
| | 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. |
+----------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
: Patterns (principes) GRASP {#tbl-tableGRASP tbl-colwidths=\[25,75\]}
## GRASP et RDCU
Les principes GRASP sont utilisés dans les réalisations de cas d'utilisation (RDCU). On s'en sert pour annoter les décisions de conception, pour rendre explicites (documenter) les choix. Voir la section [Réalisations de cas d'utilisation (RDCU)](#sec-rdcu) pour plus d'informations.
## GRASP et patterns GoF
On peut voir les principes GRASP comme des généralisations (principes de base) des patterns GoF. Voir la section [Décortiquer les patterns GoF avec GRASP](#sec-GRASP-GoF) pour plus d'informations.
## Exercices
::: {#exr-GRASP_GOF_polymorphisme}
### GRASP Polymorphisme
Soit le diagramme de classe sur la @fig-GRASP_polymorphisme_ex modélisant l'exemple de @Fowl18a à [*Replace Conditional with Polymorphism*](https://refactoring.com/catalog/replaceConditionalWithPolymorphism.html).
```{.plantuml genurl="true" #fig-GRASP_polymorphisme_ex caption='Classe "Bird" à laquelle on peut appliquer le principe GRASP Polymorphisme.'}
@startuml
!include normal.pumlinclude
class Bird {
type : String
numberOfCoconuts : number
voltage : number
getPlumage() : String
}
@enduml
```
Appliquez le GRASP Polymorphisme pour le code suivant:
```javascript
get plumage() {
switch (this.type) {
case 'EuropeanSwallow':
return "average";
case 'AfricanSwallow':
return (this.numberOfCoconuts > 2) ? "tired" : "average";
case 'NorwegianBlueParrot':
return (this.voltage > 100) ? "scorched" : "beautiful";
default:
return "unknown";
}
}
```
:::: {.callout-note collapse="true" icon="false"}
## Voici une solution
Puisque le comportement de `getPlumage` change en fonction du type de l'oiseau, il est préférable de créer une classe concrète pour chaque oiseau (`EuropeanSwallow`, `AfricanSwallow`, `NorwegianBlueParrot`) afin d'améliorer la cohésion du code.
L'utilisation d'une classe abstraite `Bird` permet de garder un faible couplage malgré l'ajout de plusieurs nouvelles classes.
Par le principe de polymorphisme, les oiseaux concrets sont aussi considérés comme des `Bird`.
Puisque les oiseaux ne partagent pas de propriétés ou d'implémentation, il aurait aussi été possible d'utiliser une interface pour la classe `Bird`.
```{.plantuml genurl="true" #fig-GRASP_polymorphisme_sol caption='Classes "Bird" auxquelles on a appliqué le principe GRASP Polymorphisme.'}
@startuml
!include normal.pumlinclude
hide empty members
abstract Bird {
{abstract} getPlumage() : String
}
class EuropeanSwallow extends Bird
class AfricanSwallow extends Bird {
numberOfCoconuts : number
}
class NorwegianBlueParrot extends Bird {
voltage : number
}
@enduml
```
```javascript
abstract class Bird {
abstract get plumage(): string
}
class EuropeanSwallow extends Bird {
get plumage() {
return "average"
}
}
class AfricanSwallow extends Bird {
_numberOfCoconuts: number;
constructor(numberOfCoconuts: number) {
super();
this._numberOfCoconuts = numberOfCoconuts;
}
get plumage() {
return (this.numberOfCoconuts > 2) ? "tired" : "average";
}
get numberOfCoconuts() {
return this._numberOfCoconuts;
}
}
class NorwegianBlueParrot extends Bird {
_voltage: number;
constructor(voltage: number) {
super();
this._voltage = voltage;
}
get plumage() {
return (this.voltage > 100) ? "scorched" : "beautiful";
}
get voltage() {
return this._voltage;
}
}
```
::::
:::
<!-- ::: {#exr-GRASP_GOF_cohesion}
### GRASP Cohésion
Cet exercice est inspiré d'un exemple donné dans le livre *Head First Design Patterns* de @FreemanBatesSierraRobson200410.
- Quelles classes sur la @fig-GRASP_cohesion_ex1 ont de multiples responsabilités?
- Déterminez si les classes sur la @fig-GRASP_cohesion_ex2 ont une cohésion forte ou faible.
```{.plantuml genurl="true" #fig-GRASP_cohesion_ex1 caption="Quelles classes ont de multiples responsabilités?"}
@startuml
!include normal.pumlinclude
class Jeu {
login()
inscrire()
déplacer()
tirer()
rester()
}
class Joueur {
setNom()
setAdresse()
setNuméroTéléphone()
sauvegarder()
charger()
}
class Téléphone {
composer()
raccrocher()
parler()
envoyerDonnées()
flash()
}
class JeuCartes {
hasNext()
next()
remove()
addCard()
removeCard()
shuffle()
}
class Panier {
ajouter()
enlever()
encaisser()
sauvegarder()
}
class Iterator {
hasNext()
next()
remove()
}
@enduml
```
```{.plantuml genurl="true" #fig-GRASP_cohesion_ex2 caption="Quelles classes ont une cohésion forte? Une cohésion faible?"}
@startuml
!include normal.pumlinclude
class Jeu {
login()
inscrire()
déplacer()
tirer()
rester()
getMeilleureScore()
getNom()
}
class SessionPartie {
login()
inscrire()
}
class ActionsJoueur {
déplacer()
tirer()
rester()
}
class Joueur {
getMeilleurScore()
getNom()
}
@enduml
```
::: -->
::: {#exr-GRASP_GOF_contrôleur}
### GRASP Contrôleur, Fabrication Pure et Indirection
- Expliquez pourquoi le principe du GRASP Contrôleur dans le cas d'un contrôleur de session (de cas d'utilisation) est une Fabrication Pure.
- Soit un contrôleur de session `GestionnaireVentes`; quelle est la "situation désespérée" qui est mitigée par ce contrôleur? Astuce: relire la définition de Fabrication Pure.
- Expliquez pourquoi le principe du GRASP Contrôleur dans le cas d'un contrôleur de session est un exemple du GRASP Indirection.
:::
::: {#exr-GRASP_GOF_expert}
### GRASP Expert en Information et Forte Cohésion
Appliquez le GRASP Expert en Information pour améliorer la conception des classes dans le code suivant:
```javascript
class Point {
x:number;
y:number;
constructor(x:number, y:number) {
this.x = x;
this.y = y;
}
}
class Circle {
center:Point;
radius:number;
constructor(center:Point, radius:number) {
this.center = center;
this.radius = radius
}
}
class SomeClass {
someMethod() {
const center = new Point(0, 12);
const c = new Circle(center, 14);
const area = Math.PI * c.radius* c.radius;
const diameter = c.radius * 2;
const circumference = Math.PI * c.radius * 2;
}
}
```
:::: {.callout-note collapse="true" icon="false"}
## Voici une solution
Puisque la classe `Circle` possède les informations nécessaires (`radius`) pour calculer `area`, `diameter` et `circumference`, c'est elle qui doit contenir les méthodes pour calculer ces propriétés.
Cela augmente la cohésion de la classe `SomeClass` puisqu'elle a moins de responsabilités.
La complétude de la classe `Circle` est améliorée et ses méthodes peuvent être testées et réutilisées dans d'autres classes.
Pour être concis, la classe `Point` est omise puisqu'aucune modification n'y est apportée.
```javascript
class Circle {
center:Point;
radius:number;
constructor(center:Point, radius:number) {
this.center = center;
this.radius = radius
}
get area():number {
return Math.PI * this.radius * this.radius;
}
get diameter():number {
return this.radius * 2;
}
get circumference():number {
return Math.PI * this.radius * 2;
}
}
class SomeClass {
someMethod() {
const center = new Point(0, 12);
const c = new Circle(center, 14);
const area = c.area;
const diameter = c.diameter;
const circumference = c.circumference;
}
}
```
::::
:::
::: {#exr-GRASP_GOF_fabrication_pure_indirection_fabrique}
### GRASP Fabrication Pure et Indirection
Appliquez le GRASP Fabrication Pure au contrôleur `BirdManager` pour améliorer sa cohésion.
Ajoutez une fabrique de `Bird` à qui le contrôleur pourra déléguer la responsabilité de créer les objets `Bird`.
Par la suite, expliquez pourquoi la fabrique que vous avez ajoutée est un exemple des GRASP Indirection et Fabrication Pure.
```javascript
class BirdManager {
addBird(b: {type: String, numberOfCoconuts: number, voltage: number}) {
let bird: Bird;
if (b.type == "EuropeanSwallow") {
bird = new EuropeanSwallow();
}
else if (b.type == "AfricanSwallow") {
bird = new AfricanSwallow(b.numberOfCoconuts);
}
else if (b.type == "NorwegianBlueParrot") {
bird = new NorwegianBlueParrot(b.voltage)
}
else {
throw new Error("Unsupported bird.");
}
// Then, we do something with the bird...
}
}
```
:::: {.callout-note collapse="true" icon="false"}
## Voici une solution
L'ajout de la fabrique `BirdFactory` permet d'améliorer la cohésion du contrôleur `BirdManager` puisque ses responsabilités sont réduites.
La fabrique est un exemple du GRASP Indirection puisqu'elle élimine le couplage direct entre le contrôleur `BirdManager` et les différents types de `Bird`.
Si d'autres types de `Bird` sont ajoutés à l'application, l'impact sera moins important sur le contrôleur.
La fabrique est une Fabrication Pure, car elle n'existe pas dans le domaine de l'application.
Elle a été inventée pour améliorer la cohésion de `BirdManager` et réduire son couplage avec les `Bird` concrets.
```javascript
class BirdManager {
addBird(b: {type: String, numberOfCoconuts: number, voltage: number}) {
let bird: Bird = BirdFactory.createBird(b);
// Then, we do something with the bird...
}
}
class BirdFactory {
public static createBird(b: {type: String, numberOfCoconuts: number,
voltage: number}) {
let bird: Bird;
if (b.type == "EuropeanSwallow") {
bird = new EuropeanSwallow();
}
else if (b.type == "AfricanSwallow") {
bird = new AfricanSwallow(b.numberOfCoconuts);
}
else if (b.type == "NorwegianBlueParrot") {
bird = new NorwegianBlueParrot(b.voltage)
}
else {
throw new Error("Unsupported bird.");
}
return bird;
}
}
```
::::
:::
::: {#exr-GRASP_GOF_expert_createur_forte_cohesion}
### GRASP Expert en Information et Forte Cohésion
Appliquez le GRASP Expert en Information aux classes `Movie`, `Person` et `MovieManager` pour améliorer leur cohésion.
Pour être concis, les constructeurs, les accesseurs et les mutateurs des classes `Movie` et `Person` sont omis.
```javascript
class Movie {
_id:String
_title:String
_synopsis:String
_year:number
_duration:number
_genres:String[]
_directors:Person[]
}
class Person {
_name:String
_biography:String
}
class MovieManager {
public getMovie(String id) {
const movie:Movie = movies.get(id); // We get the movie from the map
// We convert the movie to JSON
var movieJSON = {
id: movie.id,
title: movie.title,
synopsis: movie.synopsis,
year: movie.year,
duration: movie.duration,
genres: movie.genres,
directors: []
};
// We convert the directors to JSON and we add them to JSON representation of the movie
for (var director:Person of movie.directors) {
var directorJSON = {
name: director.name,
biography: director.biography
};
movieJSON.directors.append(directorJSON);
}
return movieJSON;
}
}
```
:::: {.callout-note collapse="true" icon="false"}
## Voici une solution
Puisque les classes `Movie` et `Person` possèdent les informations nécessaires pour se convertir en JSON, ce sont elles qui doivent posséder les méthodes pour effectuer la conversion.
Cela augmente la cohésion de la classe `MovieManager` puisqu'elle a moins de responsabilités.
La complétude des classes `Movie` et `Person` est améliorée et leurs méthodes peuvent être testées et réutilisées dans d'autres classes.
```javascript
class Movie {
_id:String
_title:String
_synopsis:String
_year:number
_duration:number
_genres:String[]
_directors:Person[]
public toJSON() {
return {
id: this._id,
title: this._title,
synopsis: this._synopsis,
year: this._year,
duration: this._duration,
genres: this._genres,
directors: this._directors // The conversion to JSON will be done automatically
};
}
}
class Person {
_name:String
_biography:String
public toJSON() {
return {
name: this._name,
biography: this._biography
};
}
}
class MovieManager {
public getMovie(id:String) {
const movie:Movie = movies.get(id); // We get the movie from the map
return movie.toJSON();
}
}
```
::::
:::
::: {#exr-GRASP_GOF_expert_createur_forte_cohesion_2}
### GRASP Expert en Information et Forte Cohésion
Appliquez le GRASP Expert en information aux classes `Book` et `BookManager` pour améliorer leur cohésion.
Pour être concis, les accesseurs et les mutateurs de la classe `Book` sont omis.
```javascript
class Book {
_title:String
_year:number
_pages:number
_isbn:String
constructor(title:String, year:number, pages:number, isbn:String) {
this._title = title;
this._year = year;
this._pages = pages;
this._isbn = isbn;
}
}
class BookManager {
public addBook(title:String, year:number, pages:number, isbn:String) {
title = title.trim();
year = parseInt(year);
pages = parseInt(pages);
isbn = isbn.trim();
if (title.length == 0) {
throw new Error("The book title must not be empty.");
}
if (year > (new Date()).getFullYear()) {
throw new Error("You cannot add a book that will be published in the future.");
}
if (pages <= 0) {
throw new Error("The book must contain at least one page.");
}
if (!(isbn.length == 10 || isbn.length == 13)) {
throw new Error("ISBN must have 10 or 13 characters.");
}
const book = new Book(title, year, pages, isbn);
// We add the book to the map
books.add(isbn, book);
}
}
```
:::: {.callout-note collapse="true" icon="false"}
## Voici une solution
Puisque la classe `Book` contient les propriétés `title`, `year`, `pages` et `isbn`, c'est sa responsabilité de déterminer ce qui est une information erronée et d'effectuer les validations.
Cela augmente la cohésion de la classe `BookManager` puisqu'elle a moins de responsabilités.
De plus, si on doit à modifier les informations d'une livre existant, il ne sera pas nécessaire de dupliquer les validations.
La complétude de la classe `Book` est améliorée et les validations peuvent être testées à l'aide de tests unitaires.
```javascript
class Book {
_title:String
_year:number
_pages:number
_isbn:String
constructor(title:String, year:number, pages:number, isbn:String) {
this._title = title;
this._year = year;
this._pages = pages;
this._isbn = isbn;
}
public get title() {
return this._title;
}
public set title(title:String) {
title = title.trim();
if (title.length == 0) {
throw new Error("The book title must not be empty.");
}
this._title = title;
}
public get year() {
return this._year;
}
public set year(year:number) {
year = parseInt(year);
if (year > (new Date()).getFullYear()) {
throw new Error("You cannot add a book that will be published in the future.");
}
this._year = year;
}
public get pages() {
return this._pages;
}
public set pages(pages:number) {
pages = parseInt(pages);
if (pages <= 0) {
throw new Error("The book must contain at least one page.");
}
this._pages = pages;
}
public get isbn() {
return this._isbn;
}
public set isbn(isbn:String) {
isbn = isbn.trim();
if (!(isbn.length == 10 || isbn.length == 13)) {
throw new Error("ISBN must have 10 or 13 characters.");
}
this._isbn = isbn;
}
}
class BookManager {
public addBook(title:String, year:number, pages:number, isbn:String) {
const book = new Book(title, year, pages, isbn);
// We add the book to the map
books.add(isbn, book);
}
}
```
::::
:::
::: {#exr-GRASP_GOF_polymorphisme_createur_fabrication_pure_indirection}
### GRASP Polymorphisme, Créateur, Fabrication Pure et Indirection
Appliquez les GRASP Polymorphisme et Créateur à la classe `PartManager` pour améliorer sa cohésion et réduire son couplage.
Créez une interface `PartParser` qui contient les méthodes pertinentes aux parsers et qui sera implémentée par ces derniers.
Ajoutez un `PartParser` concret pour chaque type de fichier traitée par la méthode `parsePart`.
Ajoutez une fabrique de `PartParser` à qui `PartManager` pourra déléguer la responsabilité de créer les objets `PartParser`.
Par la suite, expliquez pourquoi la fabrique que vous avez ajoutée est un exemple des GRASP Indirection et Fabrication Pure.
```javascript
class Part {
name:string
description:String
constructor(name:string, description:String) {
this.name = name;
this.description = description;
}
}
class PartManager {
public parsePart(fileType:string, content:string) {
var part:Part;
if (fileType == "csv") {
const partsInfo = content.split(",");
part = new Part(partsInfo[0], partsInfo[1]);
}
else if (fileType == "json") {
const part = JSON.parse(content);
part = new Part(part.name, part.description);
}
else if (fileType == "xml") {
var parser = new DOMParser();
var xml = parser.parseFromString(content, "application/xml");
var name = xml.getElementsByTagName("name")[0].childNodes[0].nodeValue || ""; // Return an empty name if it is not in the XML tree
var description = xml.getElementsByTagName("description")[0].childNodes[0].nodeValue || ""; // Return an empty description if it is not in the XML tree
part = new Part(name, description);
}
else {
throw new Error("Unsupported file type");
}
// Then, we do something with the part...
}
}
```
:::: {.callout-note collapse="true" icon="false"}
## Voici une solution
La création de plusieurs `PartParser` permet d'améliorer la cohésion de la classe `PartManager` puisqu'elle a moins de responsabilités.
Les `PartParser` peuvent être plus facilement réutilisés et testés à l'aide de tests unitaires.
La création de l'interface `PartParser` permet de créer un API stable pour les `PartParser`, ce qui réduit le couplage entre `PartManager` et les différents types de `PartParser`.
La fabrique est un exemple du GRASP Indirection puisqu'elle élimine le couplage direct entre la classe `PartManager` et les différents types de `PartParser`.
Si d'autres types de `PartParser` sont ajoutés à l'application, l'impact sera moins important sur le contrôleur.
La fabrique est une Fabrication Pure, car elle n'existe pas dans le domaine de l'application.
Elle a été inventée pour améliorer la cohésion de `PartManager` et réduire son couplage avec les `PartParser` concrets.
```javascript
class Part {
name:string
description:String
constructor(name:string, description:String) {
this.name = name;
this.description = description;
}
}
class PartManager {
public parsePart(fileType:string, content:string) {
var part:Part = ParserFactory.createParser(fileType, content).parse();
// Then, we do something with the part...
}
}
interface PartParser {
public parse():Part
}
class CSVPartParser implements PartParser {
public parse():Part {
const partsInfo = content.split(",");
return new Part(partsInfo[0], partsInfo[1]);
}
}
class JSONPartParser implements PartParser {
public parse():Part {
const part = JSON.parse(content);
return new Part(part.name, part.description);
}
}
class XMLPartParser implements PartParser {
public parse():Part {
var parser = new DOMParser();
var xml = parser.parseFromString(content, "application/xml");
var name = xml.getElementsByTagName("name")[0].childNodes[0].nodeValue || ""; // Return an empty name if it is not in the XML tree
var description = xml.getElementsByTagName("description")[0].childNodes[0].nodeValue || ""; // Return an empty description if it is not in the XML tree
return new Part(name, description);
}
}
class ParserFactory {
public static createParser(fileType:string, content:string) {
if (fileType == "csv") {
return new CSVPartParser(content);
}
else if (fileType == "json") {
return new JSONPartParser(content);
}
else if (fileType == "xml") {
return new XMLPartParser(content);
}
else {
throw new Error("Unsupported file type");
}
}
}
```
::::
:::
::: {#exr-GRASP_GOF_protection_variations_fabrication_pure}
### GRASP Protection des Variations et Fabrication Pure
Le code suivant permet de gérer les emprunts de livres pour une bibliothèque.
À chaque emprunt un événement est ajouté dans le calendrier Google de l'emprunteur pour lui rappeler de retourner le livre à temps.
Pour ce faire, l'API de Google Calendar est utilisé.
Appliquez les GRASP Protection des Variations et Fabrication Pure pour améliorer la cohésion de `ReservationManager`.
Pour être concis, la gestion des réservations dans l'application est omise.
```javascript
class ReservationManager {
public addReservation(String memberId, String bookId, String bookCopyId, Date expiration) {
var member = members.get(memberId);
var book = books.get(bookId);
// We assume that we create the reservation for the member some way...
// Then we add an event to the Google Calendar of the member to remind them to return their book
var dateAsString = expiration.toLocaleDateString("fr-CA", {year: "numeric", month: "2-digit", day: "2-digit"});
// References
// https://developers.google.com/calendar/api/v3/reference?hl=fr
// https://dev.to/pedrohase/create-google-calender-events-using-the-google-api-and-service-accounts-in-nodejs-22m8
var event = {
"summary": `Your reservation for ${book.getTitle()} is expired`,
"location": "475 Boul. de Maisonneuve E, Montréal, QC H2L 5C4",
"description": "You must return your book today to avoid late fees.",
"start": {
"date": dateAsString,
"timeZone": "America/Toronto"
},
"end": {
"date": dateAsString,
"timeZone": "America/Toronto"
},
"reminders": {
"useDefault": false,
"overrides": [
// Send a reminder the day before by email and push notification
{"method": "email", "minutes": 24 * 60},
{"method": "popup", "minutes": 24 * 60}
]
}
};
// Create a client that we can use to communicate with Google
const client = new JWT({
email: member.email,
key: member.googlePrivateKey,
scopes: [
"https://www.googleapis.com/auth/calendar",
"https://www.googleapis.com/auth/calendar.events",
],
});
const calendar = google.calendar({ version: "v3" });
// We make a request to Google Calendar API.
const res = await calendar.events.insert({
calendarId: "primary",
auth: client,
requestBody: event,
});
}
}
```
:::: {.callout-note collapse="true" icon="false"}
## Voici une solution
La classe `GoogleCalendar` est une Fabrication Pure, car elle n’existe pas dans le domaine de l’application.
Elle a été ajoutée pour améliorer la cohésion de `ReservationManager`.
Avec la classe `GoogleCalendar`, il est aussi plus facile de réutiliser la fonctionnalité dans une autre partie de l'application.