I. Quelques avantages des Builders▲
Un petit rappel pour commencer : un Builder est une class qui permettra la construction d'une autre class en fournissant une API fluent. Chaque méthode renvoie l'instance du Builder permettant ainsi les invocations chaînées. Les différentes invocations peuvent se lire comme une phrase, d'où le nom de fluent. Une fois les différents paramètres renseignés, on invoquera une method de construction (souvent build()).
Les builders ne se contentent pas de faciliter l'écriture des invocations avec une API fluent : avec leurs APIs permettant de décrire simplement les paramètres par le nom des methods, ils évitent ces tristes moments de solitude ressentis par les développeurs qui doivent invoquer des constructeurs avec cinq Strings et deux Booleans…
Ils facilitent aussi l'écriture des cas de tests de construction de nos objets (ou d'erreurs de construction) puisqu'il est très facile de construire une instance de Builder complète et correcte dans les tests et de changer un seul paramètre pour tester un cas précis :
2.
3.
4.
5.
6.
7.
8.
9.
10.
@Test
void
shouldNotBuildWithoutName
(
) {
assertThatThrownBy
((
) ->
fullBuilder
(
).name
(
null
).build
(
))
.isExactlyInstanceOf
(
MissingMandatoryValueException.class
)
.hasMessageContaining
(
"name"
);
}
// ...
private
static
BranchBuilder fullBuilder
(
Branch target) {
// Ici, le code complet pour construire un builder
}
Les builders peuvent aussi être de bons alliés de nos algorithmes puisqu'ils sont mutables et peuvent permettre la construction d'objets immuables. Par exemple, si l’on veut construire un objet en parsant un XML avec un parseur SAX (oui, même aujourd'hui on est parfois obligé de faire ça…), il sera très pratique de renseigner un Builder jusqu'à trouver notre balise fermante pour finalement construire notre objet.
Pour ces raisons, j'utilise aujourd'hui beaucoup (trop ?) de Builders dans la construction des produits sur lesquels je travaille et je ne peux que vous inviter à en faire autant ! (Même si c'est un peu verbeux, il faut bien l'admettre.)
II. Des Builders simples▲
Il existe pléthore de manières de faire des Builders. Après en avoir essayé certaines, voici ce que j'aime faire actuellement :
- une static factory du Builder dans la class qui doit être construite ;
- une static class de Builder dans le même fichier que la class à construire ;
- des methods d'affectation des valeurs dans le Builder qui portent simplement le nom de l'attribut ;
- une method build dans le Builder qui invoque un constructeur privé de la class à construire en passant l'instance du Builder.
Si je veux faire un Builder pour Branch qui se construit avec un name, une description et un owner, j'aurais donc :
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.
public
class
Branch {
private
final
String name;
private
final
Optional<
String>
description;
private
final
Username owner;
private
Branch
(
BranchBuilder builder) {
// Fields controls
name =
builder.name;
description =
Optional.ofNullable
(
builder.description);
owner =
new
Username
(
builder.owner);
}
public
static
BranchBuilder builder
(
) {
return
new
BranchBuilder
(
);
}
// Business methods
public
static
class
BranchBuilder {
private
String name;
private
String description;
private
String owner;
public
BranchBuilder name
(
String name) {
this
.name =
name;
return
this
;
}
public
BranchBuilder description
(
String description) {
this
.description =
description;
return
this
;
}
public
BranchBuilder owner
(
String owner) {
this
.owner =
owner;
return
this
;
}
public
Branch build
(
) {
return
new
Branch
(
this
);
}
}
}
Dans cet exemple, j'ai fait le choix de manipuler des primitives dans mon Builder et de faire la création des objets dans le constructeur de la class. Il est aussi possible de prendre directement les objets en paramètre des méthodes du Builder.
Je trouve que cette manière de faire a de nombreux avantages :
- c'est la class que l'on construit qui garde le contrôle sur le contenu des données lors de la construction ;
- on ne peut construire la class qu'en passant par son Builder ;
- la complexité du code du Builder est extrêmement réduite.
III. Les Builders et l'héritage▲
Un des très gros défauts de l'approche précédente est qu'elle bloque totalement les extensions. Il est possible de faire des extensions avec des Builders en utilisant les generics :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
public
abstract
class
Field {
private
final
String key;
protected
Field
(
FieldBuilder<
?, ?>
builder) {
// Fields controls
key =
builder.key;
}
public
abstract
static
class
FieldBuilder<
T extends
FieldBuilder<
T, U>
, U extends
Field>
{
private
String key;
@SuppressWarnings
(
"unchecked"
)
public
T key
(
String key) {
this
.key =
key;
return
(
T) this
;
}
// Other fields methods
public
abstract
U build
(
);
}
}
Et une implémentation :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
public
class
MandatoryBooleanField extends
Field {
protected
MandatoryBooleanField
(
FieldBuilder<
?, ?>
builder) {
super
(
builder);
}
public
static
class
MandatoryBooleanFieldBuilder extends
FieldBuilder<
MandatoryBooleanFieldBuilder, MandatoryBooleanField>
{
@Override
public
MandatoryBooleanField build
(
) {
return
new
MandatoryBooleanField
(
this
);
}
}
}
Même si cela semble complexe à l'écriture, à l'utilisation on utilisera ces Builders comme tous les autres !
IV. Construire toujours plus▲
Les Builders sont très répandus. La très grande majorité des Frameworks les prennent très bien en compte. Par exemple, avec Jackson, on peut désérialiser des objets utilisant des builders :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
@JsonDeserialize
(
builder =
BranchBuilder.class
)
public
class
Branch {
private
Branch
(
BranchBuilder builder) {
// Building logic
}
// Business methods
@JsonPOJOBuilder
(
withPrefix =
""
)
public
static
class
BranchBuilder {
// All methods
}
}
Il n'y a pas vraiment de limitation à l'utilisation des builders au quotidien. Globalement, les essayer c'est les adopter !
V. Remerciements▲
Cet article a été publié avec l'aimable autorisation de Colin Damon. L'article original peut être vu sur le blog de la société Ippon.
Nous remercions également Winjerome pour la mise au gabarit, Alexandre T pour la relecture orthographique de cet article.