À son niveau le plus simple, le patron builder est un moyen pratique (convenience builder) de construire un objet dont la construction demande de nombreux paramètres, dont certains peuvent être optionnels.
public class Client{
public Client(String firstName,
String lastName,
List<Email> email,
List<PhoneNumber>){
...
}
}
Client client = new Client("Arnaud",
"Carayol",
List.of("arnaud.carayol@u-pem.fr","carayol@u-mlv.fr"),
List.of());
Peu lisible, List.of() vs null, pas de valeur par défaut.
Idée: On va introduire une classe mutable qui a pour fonction créer l'objet. C'est le convenience builder.
public ClientBuilder {
private String firstName;
private String lastName;
private ArrayList<PhoneNumber> phones = new ArrayList<>();
private ArrayList<Email> emails = new ArrayList<>();
public ClientBuilder firstName(String firstName){
this.firstName=Objects.requireNonNull(firstName);
return this;
}
public ClientBuilder lastName(String lastName){
this.lastName=Objects.requireNonNull(lastName);
return this;
}
public ClientBuilder phoneNumber(PhoneNumber phoneNumber){
phones.add(Objects.requireNonNull(phoneNumber));
return this;
}
public ClientBuilder email(Email email){
emails.add(Objects.requireNonNull(email));
return this;
}
public Client build(){
if (firstName == null || lastName == null){
throw new IllegalStateException();
}
return new Client(firstName,lastName,emails,phones);
}
}
public class Client {
public static class Builder {
private String firstName;
private String lastName;
private ArrayList<PhoneNumber> phones = new ArrayList<>();
private ArrayList<Email> emails = new ArrayList<>();
public ClientBuilder firstName(String firstName){
this.firstName=Objects.requireNonNull(firstName);
return this;
}
public ClientBuilder lastName(String lastName){
this.lastName=Objects.requireNonNull(lastName);
return this;
}
public ClientBuilder phoneNumber(PhoneNumber phoneNumber){
phones.add(Objects.requireNonNull(phoneNumber));
return this;
}
public ClientBuilder email(Email email){
emails.add(Objects.requireNonNull(email));
return this;
}
public Client build(){
if (firstName == null || lastName == null){
throw new IllegalStateException();
}
return new Client(firstName,lastName,emails,phones);
}
}
}
Client client = new ClientBuilder().firstName("Arnaud")
.lastName("Carayol")
.email("arnaud.carayol@u-pem.fr")
.email("carayol@univ-mlv.fr")
.build();
Les méthodes du builder retournent this pour pouvoir chaîner les appels.
La méthode build effectue les vérifications
sur la consistence de l'état de l'objet.
public class Client{
public static class ClientBuilder{
...
}
private Client(ClientBuilder clientBuilder){
this.firstName = clientBuilder.firstName;
...
}
}
Le builder est souvent le seul moyen de construire l'objet.
On peut aussi utiliser le builder pour fournir des paramètres par défaut
⇛ Pas obligatoire mais c'est une possibilité.
Plutôt que de faire des new Client.Builder, on peut faire un methode static qui renvoie un Builder.
public class Client{
}
Client client = Client.with().firstName("Arnaud")
.lastName("Carayol")
.email("arnaud.carayol@u-pem.fr")
.email("carayol@univ-mlv.fr")
.build();
Les méthodes du builder retournent this pour pouvoir chaîner les appels.
La méthode build effectue les vérifications
sur la consistence de l'état de l'objet.
StringBuilderHttpRequest.Builder pour construire des requêtes HTTP.Locale.Builder pour construire les paramètres de localisation. Le patron builder du GoF est plus ambitieux. Il s'applique lorsque l'on a un produit avec plusieurs implémentations mais que toutes les implémentations ont le même schéma de construction.
Par exemple, un rapport qui peut soit être au format HTML, Markdown, XML.
Quelque soit l'implémentation, le schéma de construction est le même.
public interface ReportBuilder{
public ReportBuilder addTitle(String title);
public ReportBuilder addParagraph(String paragraph);
public ReportBuilder addSignature(String signature);
public Report build();
}
public class HTMLReportBuilder implements ReportBuilder{
private StringBuilder sb = new StringBuilder();
@Override
public ReportBuilder addTitle(String title){
sb.add("<h1>"+title+"</h1>");
return this;
}
@Override
public ReportBuilder addParagraph(String paragraph){
sb.add("<p>"+paragraph+"</p>");
return this;
}
...
@Override
public ReportBuilder build(){
return new HTMLReport(sb.toString());
}
}