À 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.
StringBuilder
HttpRequest.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()); } }