Java Design Pattern: Builder Pattern

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:

Builder pattern UML

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 questowink

 
by Alessio Mungelli Date: 17-09-2019 java sviluppo programmazione jdk jre design pattern creazionale builder creazione oggetti algoritmo visite : 615  
 
 
 
 

Articoli correlati

    Java algoritmi di ordinamento: Merge Sort

    Andiamo oggi ad analizzare uno tra i migliori algoritmi di ordinamento: il Merge Sort. Detto anche algoritmo per fusione, fa parte della famiglia dei Divide and Conquer proprio come il Quick Sort. A differenza del…

    Java algoritmi di ordinamento: Quick Sort

    Bentornati in questa nostra panoramica sul mondo Java! Oggi andremo a parlare di un algoritmo di ordinamento tra i più celebri. Il Quick Sort. A differenza del precedentemente trattato Bubble Sort, Quick…

    Flexbox, breve guida pratica

    In questo articolo impareremo a gestire gli elementi del nostro sito web in modo più efficiente ed efficace, grazie alla proprietà Flexbox. Ma attenzione, non stiamo parlando di una semplice proprietà…

    Come creare un aplicazione Vue.js in 5 minuti

    Vue.js sta acquisendo sempre più popolarità, diventando un importante concorrente di framework come Angular o React.js. Come framework per i principianti, conquista i cuori dei giovani sviluppatori front-end e delle…

    Java algoritmi di ordinamento: Bubble Sort

    Programmando, nasce spesso la necessità di ordinare le collezioni di dati o oggetti che devono poi essere manipolate. Ordinare una lista può essere utile nei casi in cui si debbano…

    Java Design Pattern: Prototype Pattern

    Andremo ora a parlare di un pattern creazionale che ci permette di "copiare con classe". Sì, anche se sembra strano, il compito fondamentale di questo pattern è copiare. Sto parlando…

    Java Design Pattern: Strategy Pattern

    Uno dei pattern che gode di una notevole popolarità ed è al contempo piuttosto semplice è lo Strategy Pattern. Membro della famiglia dei pattern comportamentali, ha il compito di gestire algoritmi,…

    Java Design Pattern: Factory Pattern

    Continuando il discorso sui design pattern iniziato precedentemente, andiamo oggi a vedere un altro pattern molto utilizzato: il Factory Method Pattern. Il GoF (Gang of Four Design Patterns) lo definisce così: Definisce…

    Java: Introduzione ai design pattern: Singleton

    Chiunque abbia anche una minima esperienza di programmazione, si sarà reso conto di come i problemi sianoricorrenti. Infatti troviamo spesso problemi con uno stesso pattern ma con contesti differenti. Ad esempio, un…

    Java 12, finalmente meno prolisso?

    Conosciamo tutti Java per le sue caratteristiche grazie alle quali, nonostante siano passati più di 20 anni dalla prima versione,è tutt'oggi uno dei linguaggi più studiati e più utilizzati, malgrado…

    JQuery morirà nel 2019?

    Per un po' di tempo, la centralità di JQuery è stata oggetto di dibattito tra gli sviluppatori web. Come programmatori web interessati a Javascript, eravamo curiosi di sapere che opinioni…

    45 utili siti che avresti voluto conoscere prima

    In rete sono presenti talmente tanti siti web dedicati alla sviluppo web e alla grafica, che risulta molto complicato conoscerli tutti. Oggi, vi proponiamo una lista di siti web non…