Builder pattern: che cos’è?
Il pattern Builder fa parte dei design pattern. Si tratta di modelli ben collaudati che facilitano il lavoro di programmazione orientata agli oggetti, in quanto permettono agli sviluppatori di non dover ripetere ogni volta i passaggi che si ripetono, dando loro la possibilità di usare una soluzione già definita. Questi elementi software risalgono al libro pubblicato nel 1994 Design Patterns: elementi per il riuso di software a oggetti dei quattro sviluppatori di software statunitensi conosciuti come “la banda dei quattro” (in inglese “Gang of Four”, abbreviato in GoF).
In questo articolo della nostra guida vi presentiamo gli aspetti essenziali dei modelli di builder design pattern, avvalendoci anche dell’aiuto di un esempio pratico.
Il builder pattern più nel dettaglio
Il builder fa parte del gruppo dei modelli di creazione conosciuto come design pattern. Migliora sia la sicurezza durante il processo di costruzione che la leggibilità del codice del programma. L’obiettivo dei modelli builder è di permettere la costruzione di un oggetto utilizzando una classe helper invece che con i soliti costruttori.
“Separate the construction of a complex object from its representation so that the same construction process can create different representations.”
Traduzione: “Separare la costruzione di un oggetto complesso dalla sua rappresentazione così che lo stesso processo di costruzione possa creare diverse rappresentazioni.” (Traduzione di IONOS)
– Gang of Four: Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides
Fonte: Design Patterns: Elements of Reusable Object-Oriented Software, casa editrice: Addison-Wesley Professional; prima edizione (10 novembre 1994)
Nel builder design pattern si distinguono quattro diversi attori:
- Director: questo attore costruisce l’oggetto complesso utilizzando l’interfaccia del costruttore. Conosce i requisiti della sequenza di lavoro del costruttore. È al livello del direttore che la costruzione di un oggetto viene disaccoppiata dal client.
- Builder: il costruttore mette a disposizione un’interfaccia per la costruzione delle componenti di un oggetto complesso (il prodotto).
- ConcreteBuilder: questo attore si occupa dell’effettiva creazione delle parti dell’oggetto complesso, definendo e gestendo la rappresentazione dell’oggetto, disponendo anche di un’interfaccia per l’output dell’oggetto.
- Product: il risultato dell’attività del builder pattern, ossia l’oggetto complesso da costruire.
Tuttavia, il passaggio decisivo di questo pattern avviene a livello del direttore, dove la creazione di un oggetto/prodotto viene separata dal client.
Il builder pattern nella rappresentazione UML
Per la rappresentazione grafica dei processi di programmazione viene usato il linguaggio di modellazione, più comunemente chiamato UML (Unified Modeling Language). La grafica qui sotto mostra come il builder pattern sia composto da numerosi oggetti che interagiscono tra loro.
Pro e contro del builder pattern
Vantaggi del Builder
La costruzione o creazione e la rappresentazione (output) vengono isolate. Le rappresentazioni interne del costruttore vengono “nascoste” dal director. Nuove rappresentazioni possono essere facilmente aggiunte grazie a nuove classi di costruzioni concrete. Il processo di costruzione viene esplicitamente gestito dal director. Nel caso in cui si debbano applicare delle modifiche lo si può fare senza dover passare per i client.
Svantaggi del Builder
Data la stretta connessione tra prodotto, ConcreteBuilder e le classi coinvolte nel processo di costruzione, può risultare difficile apportare modifiche al processo. La creazione degli oggetti richiede spesso una conoscenza delle specifiche applicazioni e del loro ambiente. L’utilizzo di pattern conosciuti e del builder può portare i programmatori a non accorgersi di soluzioni più semplici e magari anche più eleganti. Il builder pattern è perciò uno dei design pattern meno importanti tra i programmatori.
Dove viene impiegato il builder pattern?
Per rendere più intuitiva la spiegazione del builder pattern lo possiamo comparare alle dinamiche semplificate di un ristorante, dove un cliente fa la propria ordinazione. Lo staff del ristorante, che qui corrisponde ai vari attori, agisce alla ricezione dell’ordine al fine di servire al tavolo quanto richiesto. L’intero processo fino all’arrivo al tavolo del cibo ordinato da parte del cliente avviene dietro le quinte. Il cliente non vede cosa accade in cucina in seguito alla sua ordinazione, ma ne riceve solamente il risultato ultimo servito al tavolo. Nel linguaggio di programmazione questo viene comunemente denominato “print”.
Le seguenti parti di codice presentano tutti gli attori del builder pattern separatamente.
L’oggetto, il menu completo, è inizialmente vuoto; l’ordine lo riempie:
public class Menue {
private String starter = "Nessun antipasto";
private String maincourse = "Nessuna portata principale";
private String dessert = "Nessun dolce";
private String drink = "Nessuna bevanda";
public void setVorspeise(String starter) {
this.starter = starter;
}
public void setHauptgericht (String maincourse) {
this.maincourse = maincourse;
}
public void setNachspeise(String dessert) {
this.dessert = dessert;
}
public void setGetraenk(String drink) {
this.drink = drink;
}
public void print() {
System.out.println(
"Il menu è pronto! " + "\n" +
" – Antipasto: " + starter +
" – Portata principale: " + maincourse +
" – Dolce: " + dessert +
" – Bevanda: " + drink);
}
}
Il director prepara “l’ambiente” che permette la creazione di un menu da presentare al cliente. Questo ambiente è accessibile a ogni cliente. Il cliente comunica esclusivamente con il direttore, mentre l’effettiva preparazione avviene dietro le quinte:
public class MattsRestaurant {
private MenuBuilder menuBuilder;
public void setBuilder(MenuBuilder menuBuilder) {
this.menuBuilder = menuBuilder;
}
public Menu buildMenu(){
menuBuilder.buildStarter();
menuBuilder.buildMainCourse();
menuBuilder.buildDessert();
menuBuilder.buildDrink();
return menuBuilder.build();
}
}
A questo punto entra in azione il costruttore. Nell’esempio proposto si tratta dello chef del ristorante:
public abstract class MenuBuilder {
Menu menu = new Menu();
abstract void buildStarter();
abstract void buildMainCourse();
abstract void buildDessert();
abstract void buildDrink();
Menu build()
{
return menu;
}
}
Il ConcreteBuilder, il cuoco del ristorante, cucina/costruisce le singole componenti del menu ordinato. Così facendo sovrascrive (override) i punti del menu astratti e li sostituisce quindi con le pietanze:
public class MenuOfTheDayBuilder extends MenuBuilder {
@Override
public void buildStarter() {
burger.setStarter("Zuppa di zucca");
}
@Override
public void buildMainCourse() {
burger.setMainCourse("Manzo ai ferri con patatine fritte");
}
@Override
public void buildDessert() {
burger.setDessert("Gelato alla vaniglia");
}
@Override
public void buildDrink() {
burger.setDrink("Vino rosso della casa");
}
}
Infine, vengono servite all’ospite le singole portate. Questo passaggio finale viene nominato “print” nell’ambito della programmazione:
public class Main {
public static void main(String[] args) {
MattsRestaurant mattsRestaurant = new MattsRestaurant();
menuRestaurant.setBuilder(new MenuOfTheDayBuilderBuilder());
buildMenu(menuRestaurant);
menuRestaurant.setBuilder(new SpecialMenuBuilder());
buildMenu(menuRestaurant);
}
private static void buildMenu(MattsRestaurant mattsRestaurant) {
MenuOfTheDay menu = mattsRestaurant.buildMenu();
menu.print();
}
}
L’esempio qui usato si basa sui codici di Daniel Høyer Jacobsen, sul cui sito trovate illustrati diversi modelli di design pattern in Java.