Una valida alternativa al costruttore
Andiamo oggi a parlare di un pattern creazionale che in molte situazioni può rappresentare una valida alternativa alla costruzione di oggetti mediante costruttori: il Builder Pattern.
La necessità di introdurre meccanismi alternativi a quelli forniti da Java per la creazione di oggetti nasce dal fatto che talvolta le strutture sono molto complesse e non sempre è banale impostare un costruttore ben formato. Pensiamo ai casi in cui il numero di attributi sia molto alto oppure ai casi in cui ci sono attributi che possono anche non essere valorizzati. La probabilità di fare un errore scrivendo il costruttore a mano è molto alta.
L'obiettivo finale è quello di separare la creazione dell'oggetto dalla sua rappresentazione. In tale maniera l'algoritmo per la creazione dell'oggetto è indipendente dalle varie parti che costituiscono l’oggetto e da come vengono assemblate.
La creazione delle istanze e la loro gestione vengono quindi separate in modo da rendere il programma più semplice.
Un aspetto molto interessante è che questi meccanismi permettono di creare un oggetto passo passo, verificandone l'idoneità ad ogni passaggio (pensiamo a quando vogliamo costruire un oggetto con dati provenienti dai risultati di un parser) e soprattutto ci permette di nascondere la logica di controllo che sarebbe magari stata presente nell'eventuale costruttore.
Diamo una definizione:
Il Builder Pattern è usato per creare istanze di oggetti molto complessi con costruttori telescopici nella maniera più semplice
Vediamo il diagramma UML che descrive la struttura del pattern:
Analizziamo in dettaglio i vari componenti:
- Product: definisce il tipo di oggetto complesso che sarà generato dal Builder Pattern;
- Builder: questa classe astratta va a definire i vari passaggi per creare correttamente gli oggetti. Ogni metodo è generalmente astratto e le implementazioni sono fornite dalle sottoclassi concrete.Il metodo getProduct() è utilizzato per restituire il prodotto finale. Talvolta il Builder viene sostituito da un'interfaccia;
- ConcreteBuilder: possono esserci diverse sottoclassi concrete ConcreteBuilder. Queste sottoclassi forniscono i meccanismi per la creazione di oggetti complessi;
- Director: la classe Director controlla l'algoritmo per la creazione dei vari oggetti. Quando viene istanziata, il suo costruttore viene invocato. Contiene un parametro che indica quale ConcreteBuilder utilizzare per la creazione degli oggetti. Durante il processo di creazione, i vari metodi del ConcreteBuilder vengono richiamati e alla fine delle operazioni, il metodo getProduct() viene utilizzato per ottenere il prodotto finale;
Vediamo una possibile struttura in Java:
Director
public class Director { public Director(Builder builder){ builder.buildPart1(); builder.buildPart2(); builder.buildPart3(); builder.getProduct(); } }
Builder
public abstract class Builder { public abstract void buildPart1(); public abstract void buildPart2(); public abstract void buildPart3(); public abstract Product getProduct(); }
ConcreteBuilder
public class ConcreteBuilder extends Builder { private Product product; public ConcreteBuilder(){ product=new Product(); } @Override public void buildPart1() { product.setAttr1("attr1"); } @Override public void buildPart2() { product.setAttr2("attr2"); } @Override public void buildPart3() { product.setAttr3("attr3"); } @Override public Product getProduct() { return product; } }
Product
public class Product { public String attr1; public String attr2; public String attr3; public String getAttr1() { return attr1; } public void setAttr1(String attr1) { this.attr1 = attr1; } public String getAttr2() { return attr2; } public void setAttr2(String attr2) { this.attr2 = attr2; } public String getAttr3() { return attr3; } public void setAttr3(String attr3) { this.attr3 = attr3; } }
Vediamo un esempio di applicazione. L'esempio che mostrerò è tratto dal libro Effective Java di Joshua Bloch. Premettiamo che in questo particolare caso, la classe astratta Builder non è strettamente indispensabile. Può essere aggiunta come esercizio senza modificare radicalmente la struttura presentata qui sotto.
import java.util.List; public class Animal { private final String id; private String name; private String pedigreeName; private String owner; private String race; private String residence; private Boolean isVaccinated; private Boolean isChampion; private List sons; private Sex sex; private Double weight; private Double height; public Animal(String name, String pedigreeName, String id, String owner, String race, String residence, Boolean isVaccinated, Boolean isChampion, List sons, Sex sex, Double weight, Double height) { this.name = name; this.pedigreeName = pedigreeName; this.id = id; this.owner = owner; this.race = race; this.residence = residence; this.isVaccinated = isVaccinated; this.isChampion = isChampion; this.sons = sons; this.sex = sex; this.weight = weight; this.height = height; } public Animal(String id, String name, String pedigreeName) { this.name = name; this.pedigreeName = pedigreeName; this.id = id; } public Animal(String id, String owner, String race, String residence) { this.id = id; this.owner = owner; this.race = race; this.residence = residence; } public Animal(String id) { this.id = id; } public enum Sex { MALE, FEMALE } }
Applichiamo ora il pattern.
import java.util.List; public final class AnimalBuilder { private String id; private String name; private String pedigreeName; private String owner; private String race; private String residence; private Boolean isVaccinated; private Boolean isChampion; private List<String> sons; private Animal.Sex sex; private Double weight; private Double height; private AnimalBuilder(String id) { this.id = id; } public static AnimalBuilder newBuilder(String id) { return new AnimalBuilder(id); } public AnimalBuilder name(String name) { this.name = name; return this; } public AnimalBuilder pedigreeName(String pedigreeName) { this.pedigreeName = pedigreeName; return this; } public AnimalBuilder owner(String owner) { this.owner = owner; return this; } public AnimalBuilder race(String race) { this.race = race; return this; } public AnimalBuilder residence(String residence) { this.residence = residence; return this; } public AnimalBuilder isVaccinated(Boolean isVaccinated) { this.isVaccinated = isVaccinated; return this; } public AnimalBuilder isChampion(Boolean isChampion) { this.isChampion = isChampion; return this; } public AnimalBuilder sons(List<String> sons) { this.sons = sons; return this; } public AnimalBuilder sex(Animal.Sex sex) { this.sex = sex; return this; } public AnimalBuilder weight(Double weight) { this.weight = weight; return this; } public AnimalBuilder height(Double height) { this.height = height; return this; } public Animal build() { return new Animal(name, pedigreeName, id, owner, race, residence, isVaccinated, isChampion, sons, sex, weight, height); } }
Un oggetto potrà ora essere istanziato come:
Animal pluto2 = AnimalBuilder.newBuilder("0000001") .name("0000001") .pedigreeName("PlutoSecondo") .owner("Marco Rossi") .race("labrador") .residence("Via x") .isVaccinated(true) .isChampion(false) .sons(null) .sex(Animal.Sex.MALE) .weight(40.5) .height(30.0) .build();
Troviamo diversi vantaggi nell'utilizzo di questo pattern creazionale, infatti possiamo creare oggetti cloni, o comunque molto simili, minimizzando il codice da scrivere. Il metodo utilizzato è simile al seguente, facendo riferimento al builder istanziato sopra:
Animal animal3A = animalBuilder.build(); Animal animal3AClone = animalBuilder.build(); Animal animal3B = animalBuilder.sex(Animal.Sex.FEMALE).build();
Qui si creano due oggetti uguali e un oggetto simile ai due precedenti, ma con sesso opposto. Un vantaggio molto importante è quello di concentrare la validazione della classe in un unico metodo e di ottenere quindi oggetti pressochè immutabili.
Va precisato che la versione presentata è leggermente diversa da quella presentata nel modello originale. L'unico svantaggio dell'utilizzo del pattern è il fatto che vada necessariamente definita una classe builder per ogni oggetto, aumentando nettamente il tempo di sviluppo.
Molti IDE mettono a disposizione plugin per la gestione dei builder. Personalmente uso il plugin di IntelliJ Builder Generator.
Comunque, a mio parere, è sempre utile avere assi nella manica come questo
Alessio Mungelli
Computer Science student at UniTo (University of Turin), Network specializtion, blogger and writer. I am a kind of expert in Java desktop developement with interests in AI and web developement. Unix lover (but not Windows hater). I am interested in Linux scripting. I am very inquisitive and I love learning new stuffs.
Articoli correlati
Introduzione alle CSS Container Queries
Il responsive web design è una componente essenziale dello sviluppo web. Come sviluppatori front-end, dobbiamo preoccuparci continuamente della moltitudine di nuove risoluzioni e dispositivi. Va da se che creare una versione…
Un approccio a Java: switch statement
Ciao a tutti e bentornati! Dopo una pausa, torniamo oggi con un'altra parte del corso introduttivo alla programmazione, parlando di switch statement, conosciuto anche come costrutto di selezione multipla. Intuizione L'idea dello switch statement…
Un approccio a Java: Il ciclo while
Ciao a tutti e bentornati! Dopo aver fatto una breve, ma corposa, introduzione sui cicli, andiamo oggi a vedere finalmente le prime implementazioni che utilizzano quello che abbiamo definito ciclo precondizionale. In Java, come…
Un approccio a Java: I cicli - Introduzione
Ciao a tutti e bentornati! Sino ad ora, abbiamo parlato di variabili e di strutture di selezione, andando a considerare alcuni degli aspetti fondamentali di questi due concetti. Teoricamente, per…
Un approccio a Java: strutture di selezione - casi d'uso
Ciao a tutti e bentornati! Sino ad ora ci siamo preoccupati di fare una carrellata quanto più completa riguardo i concetti fondamentali di cui abbiamo bisogno per approcciarci all'utilizzo delle…
Un approccio a Java: operatori booleani
La volta precedente, abbiamo ampiamente parlato delle variabili booleane, cercando di delineare quali siano le principali operazioni che si possono effettuare proprio a livello pratico. Di tutti i casi esaminati, non abbiamo…
Un approccio a Java: le variabili booleane
Ciao a tutti e bentornati! La volta precedente, ho fatto un'introduzione alle strutture condizionali, definendo il loro funzionamento. Prima di poter dare qualche esempio pratico, è necessario chiarire in che modo ci…
Un approccio a Java: strutture condizionali
Ciao a tutti e bentornati! Le volte precedenti abbiamo introdotto il concetto di variabile, tentando di definire alcuni concetti basilari a riguardo. Alcune situazioni fanno però intuire come il solo concetto…
Un approccio a Java: Le variabili - caso d'uso
Ciao a tutti amici e bentornati! Dopo l'introduzione fatta sulle variabili, cerchiamo di analizzare alcune criticità che si possono presentare in situazioni alquanto comuni. Partiamo quindi analizzando degli esempi pratici. Esempio 1: divisione…
Un approccio a Java: Le variabili
Ciao a tutti e bentornati! Quest'oggi inizieremo un percorso che ci porterà a studiare, ed eventualmente ripassare, quelle che sono le basi della programmazione. Inizieremo parlando di variaibli. Introduzione Chiunque voglia approcciarsi al…
Hashmap con concatenamento: liste di trabocco
In questa breve serie di articoli andremo a vedere com'è possibile realizzare in C la struttura dati Hashmap. Nell'implementazione andremo ad usare le liste doppiamente concatenate come strutture dati ausiliarie. Andiamo…
Java Strutture Dati: Liste Concatenate
Con il 2020 andiamo ad esaminare un nuovo aspetto della programmazione: le strutture dati. Spesso capita a tutti di utilizzare strutture messe a disposizione dai vari linguaggi di programmazione. L'obiettivo sarà…