README.adoc 11.5 KB
Newer Older
sunye's avatar
sunye committed
1
2
3
:sectnums:
:toc:

Gerson SUNYE's avatar
Gerson SUNYE committed
4
= Distanciel SCE: Création d'une Interface _Fluent_ en Java
sunye's avatar
sunye committed
5
6
7

Pour cette partie en distanciel du cours de Construction et Évolution de Logiciels, nous allons nous intéresser à la création d'APIs lisibles et faciles à utiliser.

sunye's avatar
sunye committed
8
Plus précisément, nous verrons comment construire une  _Fluent Interface_ (en français, désignation chaînée ou chaînage de méthodes) et comment se servir de l'auto-complétion des environnements de développement pour utiliser ces interfaces.
sunye's avatar
sunye committed
9
10
11
12
13
14
15
16
17
18
19
20
21
22

WARNING: Cette partie du cours n'est *pas optionnelle* !
Pour l'examen, vous devez comprendre les principes présentés ici et être capables de refaire les exercices.

- La _première partie_ de ce distanciel est un introduction aux Interfaces Fluent.
- La _seconde partie_ de ce distanciel est un *travail pratique*, où vous créerez une Interface Fluent en Java.

== Le Pattern Fluent Builder 

En construction de logiciels, le pattern _Fluent Builder_ (en français, monteur fluide)
est particulièrement utile lorsque l'on souhaite construire des objets complexes. 

Par exemple, supposons que l'on souhaite construire un objet complexe en créant une instance de son _builder_ (monteur), 
en appelant les _setters_ (modificateurs) respectifs, et enfin, en appelant la méthode de construction. 
sunye's avatar
sunye committed
23
Une fois la méthode de construction exécutée, on peut récupérer l'objet de la classe souhaitée.
sunye's avatar
sunye committed
24
25
26
27
28
29
30
31

```java
EmailMessageBuilder builder = new EmailMessageBuilder();
EmailMessage message = builder.buildEmailMessage();
message.setFrom("gerson.sunye@univ-nantes.fr");
message.seTo("licence.informatique@univ-nantes.fr");
message.setSubject("Distanciel");
message.setBody("Au travail !!!");
Gerson Sunyé's avatar
Gerson Sunyé committed
32
message.build();
sunye's avatar
sunye committed
33
34
35
```

Ce code semble très simple, mais il y a une difficulté potentielle:
Thibault BEZIERS-LA-FOSSE's avatar
Thibault BEZIERS-LA-FOSSE committed
36
si la construction de l'objet est complexe et qu'il y a trop d'attributs à modifier, le développeur peut en oublier certains et créer un objet incomplet.
sunye's avatar
sunye committed
37

Thibault BEZIERS-LA-FOSSE's avatar
Thibault BEZIERS-LA-FOSSE committed
38
39
Dans de nombreuses applications, il y a souvent une entité centrale, comme une "Commande", un "Produit" ou un "Service", dont les instances sont créées dans différents morceaux de code.
Dans ces cas, l'oubli d'initialiser un attribut peut s'avérer très couteux en termes de déboggage et de maintenance.
sunye's avatar
sunye committed
40
41
42
43
44
45
Une question se pose: 

[quote]
Comment aider le développeur à se rappeler de tous les attributs, obligatoires et optionnels?

La réponse est de forcer le développeur à initialiser tous les attributs obligatoires *avant* d'appeler la méthode de construction. 
Gerson Sunyé's avatar
Gerson Sunyé committed
46
47
Ainsi, tous les attributs obligatoires seront initialisés et l'objet créé sera complet.
Ce qui soulève une nouvelle question:
sunye's avatar
sunye committed
48

Gerson Sunyé's avatar
Gerson Sunyé committed
49
50
[quote]
Comment forcer le développeur à initialiser correctement un objet?
sunye's avatar
sunye committed
51

Gerson Sunyé's avatar
Gerson Sunyé committed
52
La réponse est simple : en utilisant le patron _Fluent Builder_. 
sunye's avatar
sunye committed
53

Thibault BEZIERS-LA-FOSSE's avatar
Thibault BEZIERS-LA-FOSSE committed
54
L'idée c'est qu'au lieu de précéder chaque appel de méthode par le nom de l'objet --ce qui conduit à un style verbeux-- le nom de l'objet reste actif dans toute l'instruction en cours. 
sunye's avatar
sunye committed
55
56
57
58
59
60
61
62
63
64
65
66
67
Le code devient ainsi à la fois plus concis et plus clair.
Par exemple:

```java
EmailMessage message = EmailMessageBuilder.builder()
    .from("gerson.sunye@univ-nantes.fr")
    .to("licence.informatique@univ-nantes.fr")
    .subject("Distanciel")
    .body("Au travail !!!")
    .build();
```


Gerson Sunyé's avatar
Gerson Sunyé committed
68
La réduction de verbosité est semblable à celle de l'expression `with` en Pascal, bien que basée sur des principes différents.
sunye's avatar
sunye committed
69
70
71
72
73

=== Chainage de méthodes

Ce style (ou idiome) de programmation, appelé _chainage de méthodes_ a été introduit par le langage Smalltalk, où par convention les modificateurs retournent l'objet modifié (`this`).

Gerson Sunyé's avatar
Gerson Sunyé committed
74
Dans le cas de Java, la logique est similaire: toutes les méthodes de modification (`from`, `to`, etc.) seront spécifiées dans la `Builder` et retourneront une instance de cette classe.
sunye's avatar
sunye committed
75

sunye's avatar
sunye committed
76
Par exemple, la méthode `from()` sera mise en œuvre comme suit:
sunye's avatar
sunye committed
77
78
79
80
81
82
83
84

```java
public EmailMessageBuilder from(String sender) {
    this.from = sender;
    return this;
}
```

sunye's avatar
sunye committed
85
Quant à la méthode `to()`, sa mise en œuvre sera semblable à la précédente:
sunye's avatar
sunye committed
86
87
88
89
90
91
92
93

```java
public EmailMessageBuilder to(String recipient) {
    this.to = recipient;
    return this;
}
```

Thibault BEZIERS-LA-FOSSE's avatar
Thibault BEZIERS-LA-FOSSE committed
94
Si le chaînage de méthodes permet de simplifier le code de création d'objets, il ne peut pas empêcher l'oubli de certains attributs, ni l'appel de la méthode `build()` avant que tous les attributs soient initialisés.
sunye's avatar
sunye committed
95
96
97
98
99
100
101
102
103
104
Une nouvelle question se pose alors:


[quote]
Comment empêcher le développeur d'appeler la méthode de construction avant d'initialiser tous les attributs obligatoires ?

Pour répondre à cette question, nous allons utiliser le chaînage d'interfaces.

=== Le chaînage d'interfaces

Thibault BEZIERS-LA-FOSSE's avatar
Thibault BEZIERS-LA-FOSSE committed
105
La logique du chaînage d'interfaces est de limiter l'accès lors du chaînage de méthodes, en créant une interface pour chaque attribut obligatoire et en modifiant le type de retour de chaque modificateur pour le faire retourner la prochaine interface.
sunye's avatar
sunye committed
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
Compliqué ? Oui, en effet, mais la logique deviendra plus claire en regardant le code des interfaces:

```java
interface EmailFrom {
    EmailTo from(String sender);
}

interface EmailTo {
    EmailSubject to(String recipient);
}

interface EmailSubject {
    EmailBody subject(String subject);
}

interface EmailBody {
    EmailCreator body(String contents);
}

interface EmailCreator {
    EmailMessage build();
}
```

Gerson Sunyé's avatar
Gerson Sunyé committed
130
Notez que dans cette suite d'interfaces, chaque modificateur a comme type de retour l'interface suivante dans la suite.
sunye's avatar
sunye committed
131
132
133
134
135
136
137
138
Ainsi, pour atteindre l'interface `EmailCreator`, on doit passer par une suite d'interfaces, ce qui oblige le développeur à appeler différentes méthodes avant de pouvoir accéder à la méthode `build()`. 

La question qu'on peut se poser maintenant est : "- Faut-il implémenter toutes ces interfaces par une nouvelle classe ?".
La réponse est simple, non, car en Java, rappelez-vous, une classe peut implémenter plus d'une interface.
C'est à la classe builder de le faire:

```java
public class EmailMessageBuilder
139
    implements EmailFrom, EmailTo, EmailSubject, EmailBody, EmailCreator {
sunye's avatar
sunye committed
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159

    }

```

Ainsi, le développeur manipule toujours le même objet, mais n'a pas toujours accès aux mêmes méthodes.
Une autre question que l'on peut se poser est : "- Comment faire lorsque certains attributs sont optionnels ?".

En effet, dans notre exemple on peut penser à ajouter ces attributs optionnels, comme le champ "CC" d'un message.
Dans ce cas, on ajoute les modificateurs de ces attributs à la dernière interface, celle qui contient la méthode `build()`:

```java
interface EmailCreator {
    EmailCreator cc(String recipient);
    EmailMessage build();
}
```

En prime, l'auto-complétion de l'IDE guidera les développeurs à travers les interfaces, en proposant par exemple, la méthode `to()`, après la méthode `from()` ou la méthode `build()` après la méthode `body()`.

sunye's avatar
sunye committed
160
161
162
163
164
165
166
== Exercice pratique : Publication de services DNS-SD

Dans cette partie, vous allez appliquer ces notions pour coder une interface fluide.

=== Préparation

. Effectuez une *divergence* du projet _distanciel_ où vous vous trouvez actuellement.
sunye's avatar
sunye committed
167
Pour rappel, il suffit d'aller utiliser le bouton correspondant en haut de la page d'accueil du projet.
sunye's avatar
sunye committed
168
169
170
171
172
173
174
175
176
. Configurez votre nouveau projet Gitlab obtenu via la divergence de la manière suivante :
** Dans _"Paramètres → Général"_, allez dans _"Visibility, project features, permissions"_, et mettez _"Project visibility"_ à _"Private"_.
Ainsi, vous devenez le seul utilisateur autorisé à accéder à votre code source.
** Dans _"Paramètres → Membres"_, ajoutez comme nouveau membre l’utilisateur virtuel appelé *Naobot*, avec le statut _"Reporter"_.
Cet utilisateur virtuel que nous contrôlons nous donne le droit d’accéder à votre travail et nous permettra de récupérer vos projets.
. Clonez avec `git` votre divergence sur votre ordinateur de travail, ce qui vous donnera un répertoire `remote`

=== Le protocole DNS-SD

Thibault BEZIERS-LA-FOSSE's avatar
Thibault BEZIERS-LA-FOSSE committed
177
DNS _Service Discovery_, ou simplement DNS-SD est un protocole de publication et de découverte de services à l'intérieur d'un réseau local, qui utilise des enregistrements DNS de type SRV, PTR et TXT.
sunye's avatar
sunye committed
178
179
180

Notre objectif ici n'est pas de comprendre le fonctionnement de ce protocole dans les détails, mais seulement de simplifier la publication de services grâce à une interface fluide.

Gerson Sunyé's avatar
Gerson Sunyé committed
181
Plus précisément, nous allons nous intéresser à la création d'instances de la classe `ServiceDescription`, dont la signature du constructeur est donnée ci-dessous:
sunye's avatar
sunye committed
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199

```java
  public ServiceDescription(String name, int port, String transport,
      String application, String subtype, int weight, int priority, int ttl,
      boolean persistent) {

    this.name         = name;
    this.port         = port;
    this.transport    = transport;
    this.application  = application;
    this.subtype      = subtype;
    this.weight       = weight;
    this.priority     = priority;
    this.ttl          = ttl;
    this.persistent   = persistent;
  }
```

sunye's avatar
sunye committed
200
* Comme nous pouvons déduire à partir du constructeur, cette classe possède 9 attributs:
Gerson SUNYE's avatar
Gerson SUNYE committed
201

Gerson SUNYE's avatar
Gerson SUNYE committed
202
203
204
205
206
** Seulement 4 sont obligatoires: `name`, `port`, `transport` et `application`.
** La valeur par défaut des attributs `weight` et `priority` est de 0.
** La valeur par défaut de l'attribut `ttl` est de 3.600.
** La valeur par défaut de l'attribut `persistent` est `false`.
** Il est possible qu'un service n'ait pas de sous-type (attribut `subtype` nul).
sunye's avatar
sunye committed
207
208
209

=== Implémentation

sunye's avatar
sunye committed
210
* Première étape:  mettre en œuvre la classe monteur, appelée  `SimpleServiceDescriptionBuilder`:
Gerson Sunyé's avatar
Gerson Sunyé committed
211
** Cette classe doit contenir exactement les mêmes attributs que la classe `ServiceDescription`.
sunye's avatar
sunye committed
212
213
** Les attributs qui ont une valeur par défaut doivent être initialisés avec cette valeur.

sunye's avatar
sunye committed
214
* Deuxième étape: mettre en œuvre le chaînage d'interfaces:
sunye's avatar
sunye committed
215
216
217
218
219
220
221
222
223
224
225
226
227
228
** Créer une interface par attribut obligatoire, plus une interface de construction: `ServiceNameModifier`,
`ServicePortModifier`, `ServiceTransportModifier`, `ServiceApplicationModifier` et enfin `ServiceDescriptionBuilder`.
** Les 4 premières interfaces doivent spécifier une seule méthode, le modificateur de l'attribut concerné.
Le type de retour de ces modificateurs doit être l'interface suivante.
**  Ainsi, le code de l'interface  `ServiceNameModifier` doit ressembler à:
+
```java
interface ServiceNameModifier {
    ServicePortModifier name(String name);
}
```

** La dernière interface, `ServiceDescriptionBuilder`, doit spécifier la méthode `build()`, dont le type de retour est `ServiceDescription`, ainsi que les modificateurs des attributs optionnels.

sunye's avatar
sunye committed
229
* Troisième étape: mettre en œuvre des interfaces dans la classe `SimpleServiceDescriptionBuilder`:
sunye's avatar
sunye committed
230

sunye's avatar
sunye committed
231
** Modifier l'en-tête de la classe monteur pour la faire implémenter les 5 interfaces définies ci-dessus.
sunye's avatar
sunye committed
232
233
234
235
236

** Implémenter chacune des  méthodes de modification, sans oublier de retourner l'objet courant.

** Implémenter enfin la méthode `build()` qui doit créer une instance de `ServiceDescription`.

sunye's avatar
sunye committed
237
* Quatrième et dernière étape: mettre en œuvre un test unitaire qui utilise votre API fluide.
sunye's avatar
sunye committed
238
239
240
241
242
243
244
245
** Pensez à vérifier que l'auto-complétion marche correctement.


=== Rendre le projet

Pour rendre le projet, il vous suffit de vous assurer d'avoir :

- bien ajouté *Naobot* comme membre _Reporter_ de votre projet.
Gerson SUNYE's avatar
Gerson SUNYE committed
246
- bien ajouté le texte "#FLUENT-SCE2020" au champ de description du projet
sunye's avatar
sunye committed
247
248
249
- bien validé et publié (commit & push) tous vos fichiers de projet 

Tant que tout cela est bien fait avant la date limite de rendu, alors tout est bon !
sunye's avatar
sunye committed
250
251
252
253
254
255
256



== Liens utiles

- https://dzone.com/articles/fluent-builder-pattern
- https://dzone.com/articles/making-java-fluent-api-more-flexible
Gerson SUNYE's avatar
Gerson SUNYE committed
257
- https://github.com/guilhermechapiewski/fluent-mail-api