La creazione dell'uomo - Michelangelo

Written by 12:47 Creational Pattern, Design Pattern, DEVeloperS, Object Scoped Pattern

Builder

Il Builder è un design pattern che offre una alternativa alla creazione di un oggetto con una struttura complessa. Come è facile intuire, il patter rientra nella categoria dei creazionali e lo potremmo paragonare ad un wizard che, passo dopo passo, ci guida lungo il processo di costruzione del prodotto.
Come consueto, la descrizione del pattern seguirà per lo più il template della GoF che abbiamo illustrato nell’introduzione ai design pattern.
Tuttavia… se ci occorre giusto una rapida occhiata, l’immagine seguente offre uno spezzato del pattern e della sua tipologia (purpose e scope). 

Intento

Separa la costruzione di un oggetto complesso dalla sua rappresentazione, in modo che lo stesso processo di costruzione consenta di istanziare differenti rappresentazioni dello stesso tipo di oggetto.

Builder

Introduzione

Le motivazioni del pattern si riescono ad intuire facilmente se ci è già capitato di aver sviluppato una classe con un buon numero di attributi.
In casi del genere, in prima battuta, non ci preoccupiamo neppure di scrivere un costruttore, affidandoci a quello onnipresente di default. Ma ci accorgiamo ben presto che ci occorrono una valanga di chiamate a metodi setter per impostare attributo su attributo.
Allora, viriamo verso una soluzione alternativa: un bel costruttore all-in-one (altresì chiamato telescopic constructor) e poco male se le regole di buon senso vogliono che i parametri di un metodo non superino le dita di una mano o poco più. Abbracciamo con una risata malefica la soluzione, da amanti sfegatati del genere horror.
Eppure, man mano che utilizziamo la classe, ci accorgiamo che anche questa soluzione è poco flessibile perché non sempre abbiamo bisogno di impostare proprio tutti gli attributi della classe e siamo costretti a lasciare a null i parametri che non ci occorrono. Non propriamente elegante come soluzione…
Cosa fare? Scrivere altri costruttori per coprire ogni combinazione? Quel po’ di matematica che ricordiamo ci dice che anche solo con una decina di attributi, potremmo dover creare più costruttori che metodi, annegando il concetto che ruota attorno alla classe nel mare delle sue differenti modi di istanziarla.
Perché allora non provare con le sottoclassi? Ogni differente rappresentazione potrebbe essere intesa come una classe figlia. Ad esempio, se pensiamo ad un oggetto Casa, potremmo avere CasaConGiardino, CasaConPiscina, CasaDiPanDiZenzero, CasaConGiardinoEPiscinaSenzaSoffittoSenzaCucina… Insomma… un’idea ancora più mostruosa della precedente! Ricordiamo sempre che, laddove possibile, si dovrebbe sempre preferire la composizione e limitare l’ereditarietà.
Finalmente, ci convinciamo di aver bisogno di un maggior grado di flessibilità. Quello che teoricamente ci occorre è un sapiente separation of concern tra:

  • la rappresentazione dell’oggetto (il cosa);
  • il suo processo di costruzione (il come).

E qui che entra in gioco il Builder.

Motivazione

Premetto che la versione del pattern qui proposto è leggermente diversa da quella della GoF. Il risultato è il medesimo ma, sfruttando il meccanismo delle fluent interface, il codice ne guadagna in leggibilità (concetto alla quale sono particolarmente legato).
Tornando al noi, per illustrare il pattern ho pensato ad un esempio culinario.
Dal momento che non nasciamo chef, ma meri software developer, una delle cose che ci riesce più semplice e naturale è imparare cose nuove (previo smodato consumo di caffè) e successivamente archiviarle. Dunque, dopo aver imparato in tempi record i fondamentali di cucina, vogliamo archiviare le nostre ricette. Se non lo facessimo, l’area heap del nostro cervello verrebbe irrimediabilmente riscritta dalle nuove nozioni di turno, facendoci perdere ogni know-how e tornare a nutrirci di junk food.
Scegliamo, dunque, di partire da un oggetto Recipe che incapsula tutte le numerose informazioni che ci possono servire come: tempi di preparazione, difficoltà, ingredienti, passi per eseguire la ricetta e quant’altro. Per applicare il pattern, creiamo una interfaccia RecipeBuilder con un insieme di metodi che singolarmente astraggono un certo step di creazione della ricetta. Inoltre, ad eccezione del metodo build che fornisce una nuova istanza di Recipe, ogni metodo restituisce un RecipeBuilder. Questo fa sì che si possa richiamare in cascata (method chaining) tutti i passi previsti per la costruzione e, solo al termine, invocare build per avere l’oggetto.

Recipe recipe = new ConcreteRecipeBuilder()
	.author("Douglas", "Adams")
	.name("Small Lump of Green Putty I Found in My Armpit One Midsummer Morning")
	.cookingTime((short) 42)
	.cost(RecipeCost.CHEAP)
	.difficulty(RecipeDifficulty.EASY)
	.numberOfPerson((short) 4)
	.preparationTime((short) 42)
	.type(CourseType.APPETIZER)
	.addIngredient("Green Putty", "1 kg")
	.addIngredient("A Vogon alive", "120 kg")
	.addPreparationStep("Put Green Putty under Vogon armpit.", 1)
	.addPreparationStep("Put the Vogon for 2 hours under scorching sunlight", 2)
	.addPreparationStep("Remove green putty and enjoy your meal!", 3)
	.tags("Vogon", "Appetizer")
		.build();

Sebbene sia opzionale ed abbastanza raro, si potrebbe spingere l’astrazione oltre. Il pattern, infatti, prevede una classe Director (nell’esempio chiamata ChefDirector) che estrae ed incapsula nei suoi metodi una serie di chiamate al builder. In altre parole, semplifica ulteriormente la creazione degli oggetti, sopratutto laddove siano presenti routine con step che si ripetono sempre uguali.
Per rendere l’idea, sappiamo che in cucina ci sono un buon numero di preparazioni di base che si ripetono sempre uguali. Basti pensare alla besciamella, al ragù o al brodo di carne o pesce. La classe director potrebbe facilitare, ad esempio, la creazione delle ricette con una base di besciamella (pensiamo alle lasagne!) aggiungendo automaticamente gli ingredienti e gli step di preparazione previsti per la base.
Tutto quello che dovrebbe fare un ipotetico client è:

  1. Creare la classe builder;
  2. Associarla al director;
  3. Lanciare la costruzione dell’oggetto attraverso il director;
  4. Recuperare il risultato tramite il builder.
(Visited 56 times, 1 visits today)

Close