Introduzione
Il design pattern Builder, come tutti i pattern creazionali, separa la costruzione di un oggetto complesso dalla sua rappresentazione cosicchΓ© il processo di costruzione stesso possa creare diverse rappresentazioni.
In particolare questo pattern Γ¨ utilizzato quando l'oggetto deve essere creato mediante vari step ed Γ¨ il builder che mantiene lo stato di tutti gli step.
Alla fine della creazione lβoggetto viene ritornato.
Il pattern builder si puΓ² riconoscere in una classe che ha un solo metodo di creazione e vari metodi per configurare lβoggetto creato.
Unβesempio classico Γ¨ la classe StringBuilder, che permette di costruire una stringa mediante vari step e fornirla solo alla fine.
Rapporto con altri pattern creazionali
Il pattern creazionale piΓΉ semplice Γ¨ il Pattern Factory e generalmente si parte sempre da quello. Se il pattern genera troppe sottoclassi spesso conviene migrare verso lβAbstract Factory, il Pattern Prototype o il Builder, che sono piΓΉ flessibili ma leggermente piΓΉ complessi.
Differenze con il pattern Factory
- Il
Buildersi focalizza sulla costruzione di un oggetto complesso step by step. Pattern Factory fornisce una determinato oggetto a partire da una famiglia in base ad un parametro in ingresso. - Anche il
BuilderpuΓ² creare vari sottoclassi in base al parametro in ingresso ma con una maggiore granularitΓ nella loro creazione: esempio se ilFactoryritorna come istanze diCarHondaeFord, ilBuilderpotrebbe ritornare unaHonda 4 cilindrie unaHonda a 6 cilindriin base alla configurazione. Il patternFactorypuΓ² essere visto come una versione semplificata delBuilder. - Il
Builderrestituisce il prodotto come passo finale del processo di creazione, mentre per quanto riguarda ilfactoryoAbstract Factory, il prodotto viene ritornato immediatamente.
Struttura
(Abstract) Builder: Γ¨ la classe astratta che viene utilizzata dallβesterno per creare le parti dellβoggettoProduct. Ha questo come variabile di stato e vari metodi, da chiamare allβesterno, per assemblarlo.ConcreteBuilder: costruisce e assembla le parti delProductimplementando lβinterfacciaBuilder. Aggiorna lβistanza della classeProductdella classe padre.Director: costruisce un oggetto utilizzando lβinterfacciaBuilder. Lo scopo del Director Γ¨ eseguire i passi della costruzione dellβoggetto in un particolare ordine, o solo alcuni passi, in base ai metodi chiamati dallβesterno. Questa classe non Γ¨ obbligatoria nel pattern in quanto il client puΓ² fare la stessa operazione, ha senso solo se ho varie configurazioni diverse dello stesso oggetto da costruire.Product: rappresenta lβoggetto complesso che voglio costruire.
Funzionamento
- Il
Clientcrea un oggettoDirectorne imposta lβoggettoBuilder; - Il
Directornotifica alBuilderse una parte del prodotto deve essere costruita in base alle richieste delClient, ilBuilderriceve le richieste dalDirectore aggiunge le parti al prodotto. - Il
Clientriceve il prodotto dalBuildere lo utilizza.
Esempio
Product
Classe Product, il prodotto complesso. In questo caso ho una lista di stringa come attributo ma nel mondo reale potrebbero essere anche oggetti piΓΉ complessi.
Notare come il costruttore sia vuoto e di come gli attributi vengano creati con un metodo (o dei setter), caratteristica tipica del pattern builder, che separa la costruzione di un oggetto dalla sua logica interna.
public class Product
{
private readonly IList<string> _parts = new List<string>();
public void Add(string part)
{
_parts.Add(part);
}
public string ListParts()
{
return $"Product parts: {string.Join(", ", _parts)}";
}
}AbstractBuilder
Il Builder Γ¨ una classe astratta che indica come bisogna creare lβoggetto Product.
In questo caso quindi indica che per creare lβoggetto Product posso crearne la parte A, B o C (o tutte insieme) tramite i metodi BuildPartA, BuildPartB e BuildPartC.
Fornisce inoltre i metodi per ottenere il product e per crearne uno nuovo.
/// <summary>
/// Il pattern builder separa la costruzione di un oggetto complesso dalla sua rappresentazione, in modo che il
/// processo di costruzione stesso possa creare diverse rappresentazioni.
/// CiΓ² ha l'effetto immediato di rendere piΓΉ semplice la classe, permettendo a una classe builder separata di
/// focalizzarsi sulla corretta costruzione di un'istanza e lasciando che la classe originale si concentri sul
/// funzionamento degli oggetti.
/// Il pattern builder si puΓ² riconoscere in una classe che ha un solo metodo di creazione e vari metodi per
/// configurare l'oggetto creato.
/// Differenze con il Factory: il Builder si focalizza sulla costruzione di un oggetto complesso "step by step".
/// Factory enfatizza una famiglia di oggetti (sia semplici che complessi).
/// Il Builder restituisce il prodotto come passo finale del processo di creazione, mentre per quanto riguarda
/// l'Abstract Factory, il prodotto viene ritornato immediatamente.
/// </summary>
public abstract class AbstractBuilder
{
protected Product Product;
/// <summary>
/// Costruttore, istanzia un nuovo <see cref="Product" />
/// </summary>
protected AbstractBuilder()
{
CreateNewProduct();
}
/// <summary>
/// Crea una nuova istanza di <see cref="Product" />
/// </summary>
public void CreateNewProduct()
{
Product = new Product();
}
/// <summary>
/// Costruisco la parte A dell'oggetto
/// </summary>
public abstract void BuildPartA();
/// <summary>
/// Costruisco la parte B dell'oggetto
/// </summary>
public abstract void BuildPartB();
/// <summary>
/// Costruisco la parte C dell'oggetto
/// </summary>
public abstract void BuildPartC();
/// <summary>
/// Questo metodo fornisce il prodotto costruito. Opzionalmente posso chiamare anche il metodo <see cref="CreateNewProduct" /> che
/// annulla l'istanza; dipende dalle esigenze
/// </summary>
public Product GetProduct()
{
var result = Product;
CreateNewProduct();
return result;
}
}Concrete Builder
Implementa lβAbstract Builder implementando solo i metodi astratti della costruzione dellβoggetto.
/// <summary>
/// Costruisce e assembla le parti del prodotto implementando l'interfaccia Builder; definisce e tiene traccia della
/// rappresentazione che crea.
/// Il builder concreto Γ¨ figlio dell'interfaccia e fornisce le implementazioni dei vari step, in questo modo posso
/// avere n builder diversi che producono lo stesso oggetto.
/// </summary>
public class ConcreteBuilder : AbstractBuilder
{
/// <summary>
/// Tutti i metodi per la creazione dell'oggetti utilizzano la stessa istanza di <see cref="Product" />
/// </summary>
public override void BuildPartA()
{
Product.Add("PartA1");
}
public override void BuildPartB()
{
Product.Add("PartB1");
}
public override void BuildPartC()
{
Product.Add("PartC1");
}
}Director
Il Director Γ¨ colui che effettivamente si occupa della creazione dellβoggetto Product a partire da un Builder.
AvrΓ quindi sempre un setter che gli imposta il tipo di Builder da utilizzare, un metodo che fornisce lβoggetto Product e un metodo che lo costruisce utilizzando i metodi del Builder.
Notare come il Director sia lβunico oggetto a sapere come si costruisce il Product, Γ¨ lui a chiamare i metodi BuildPartA, BuildPartB e BuildPartC.
/// <summary>
/// Il Director costruisce un oggetto utilizzando l'interfaccia Builder, infatti notifica al Builder se una parte
/// del prodotto deve essere costruita, il Builder riceve le richieste dal Director e aggiunge le parti al prodotto.
/// Lo scopo del Director Γ¨ eseguire i passi della costruzione dell'oggetto in un particolare ordine, o solo alcuni
/// passi, in base ai metodi chiamati dall'esterno.
/// In questo caso <see cref="BuildMinimalViableProduct" /> chiamerΓ solo un metodo del Builder, mentre
/// <see cref="BuildFullFeaturedProduct" /> chiamerΓ tutti i metodi.
/// Questa classe non Γ¨ obbligatoria nel pattern in quanto il client puΓ² fare la stessa operazione, ha senso solo se ho
/// varie configurazioni diverse dello stesso oggetto da costruire.
/// </summary>
public class Director
{
/// <summary>
/// Costruttore: imposta il <paramref name="builder" /> iniziale /// </summary> public Director(AbstractBuilder builder)
{
Builder = builder;
}
/// <summary>
/// Builder impostabile dall'esterno. Obbligo il passaggio anche a costruttore in quanto Γ¨ obbligatorio avere almeno un /// builder impostato /// </summary> public AbstractBuilder Builder { get; set; }
/// <summary>
/// Delega al builder la costruione di un nuovo <see cref="Product" />, in questo caso creando tutte le parti /// </summary> public void BuildFullFeaturedProduct()
{
Builder.CreateNewProduct();
Builder.BuildPartA();
Builder.BuildPartB();
Builder.BuildPartC();
}
/// <summary>
/// Delega al builder la costruione di un nuovo <see cref="Product" /> ma creando solo la parte A /// </summary> public void BuildMinimalViableProduct()
{
Builder.CreateNewProduct();
Builder.BuildPartA();
}
/// <summary>
/// Fornisce il <see cref="Product" /> costruito al Client /// </summary> public Product GetProduct()
{
return Builder.GetProduct();
}
}Client
Di seguito una invocazione dallβesterno del metodo Builder.
Notare che questo metodo offre i seguenti vantaggi per il client esterno:
- Il
Clientnon deve conoscere gli attributi e i componenti dellβoggetto complessoProduct - Il client deve conoscere solo le tipologie possibili Product, in questo caso che ne esiste una tipologia full optionale
BuildFullFeaturedProducte una tipologia base (BuildMinimalViableProduct). Non conosce di cosa queste pizze sono composte. - Il client deve solo creare un
Directore dirgli il tipo diProductda creare, al resto ci pensa lui - Alla fine del procedimento il
Directormi fornisce una istanza dellβoggettoProductdi cui il client non conosce alcunchΓ¨. - Se un giorno volessi creare un nuovo tipo di
Productbasta creare un nuovo oggettoConcreteBuilder, il resto rimane immutato
var director = new Director(new ConcreteBuilder());
Console.WriteLine("Product minimal with standard builder:");
director.BuildMinimalViableProduct();
Console.WriteLine(director.GetProduct().ListParts());
Console.WriteLine("Product full optional with standard builder:");
director.BuildFullFeaturedProduct();
Console.WriteLine(director.GetProduct().ListParts());
director.Builder = new CustomBuilder();
Console.WriteLine("Product minimal with custom builder:");
director.BuildMinimalViableProduct();
Console.WriteLine(director.GetProduct().ListParts());
Console.WriteLine("Product full optional with custom builder:");
director.BuildFullFeaturedProduct();
Console.WriteLine(director.GetProduct().ListParts());
// Il director Γ¨ opzionale, posso usare direttamente il builder
Console.WriteLine("Custom product:");
var builder = new ConcreteBuilder();
builder.BuildPartA();
builder.BuildPartC();
Console.Write(builder.GetProduct().ListParts());Nel mio esempio il client fornisce:
Product minimal with standard builder:
Product parts: Standard PartA1
Product full optional with standard builder:
Product parts: Standard PartA1, Standard PartB1, Standard PartC1
Product minimal with custom builder:
Product parts: Custom PartA1
Product full optional with custom builder:
Product parts: Custom PartA1, Custom PartB1, Custom PartC1
Product without builder:
Product parts: Standard PartA1, Standard PartC1-
Rider Live template
Allego il live template per utilizzare questo pattern in Rider o Visual Studio con ReSharper.
Transclude of Builder-Rider-live-template.txt