Domanda

Quali sono alcuni comuni , esempi del mondo reale dell'uso del Builder Pattern? Cosa ti compra? Perché non usare semplicemente un modello di fabbrica?

È stato utile?

Soluzione

La differenza chiave tra un costruttore e un IMHO di fabbrica è che un costruttore è utile quando è necessario fare molte cose per costruire un oggetto. Ad esempio, immagina un DOM. Devi creare molti nodi e attributi per ottenere il tuo oggetto finale. Una factory viene utilizzata quando la factory può facilmente creare l'intero oggetto all'interno di una chiamata di metodo.

Un esempio di utilizzo di un builder è la creazione di un documento XML, ho usato questo modello per costruire frammenti HTML, ad esempio potrei avere un Builder per costruire un tipo specifico di tabella e potrebbe avere i seguenti metodi (i parametri non vengono visualizzati) :

BuildOrderHeaderRow()
BuildLineItemSubHeaderRow()
BuildOrderRow()
BuildLineItemSubRow()

Questo builder quindi sputerebbe il codice HTML per me. È molto più facile da leggere che non seguire un ampio procedimento procedurale.

Dai un'occhiata a Builder Pattern su Wikipedia .

Altri suggerimenti

Di seguito sono riportati alcuni motivi che sostengono l'uso del pattern e del codice di esempio in Java, ma si tratta di un'implementazione del Builder Pattern coperto da Gang of Four in Design Patterns . Le ragioni per cui lo useresti in Java sono applicabili anche ad altri linguaggi di programmazione.

Come afferma Joshua Bloch in Java efficace, 2a edizione :

  

Il modello del builder è una buona scelta quando si progettano classi i cui costruttori o fabbriche statiche avrebbero più di una manciata di parametri.

A un certo punto tutti abbiamo incontrato una classe con un elenco di costruttori in cui ogni aggiunta aggiunge un nuovo parametro di opzione:

Pizza(int size) { ... }        
Pizza(int size, boolean cheese) { ... }    
Pizza(int size, boolean cheese, boolean pepperoni) { ... }    
Pizza(int size, boolean cheese, boolean pepperoni, boolean bacon) { ... }

Questo è chiamato il modello del costruttore telescopico. Il problema con questo modello è che una volta che i costruttori sono lunghi 4 o 5 parametri diventa difficile da ricordare il richiesto ordine dei parametri nonché quale costruttore specifico potresti desiderare in una determinata situazione.

Una alternativa al Telescoping Constructor Pattern è il JavaBean Pattern in cui si chiama un costruttore con i parametri obbligatori e quindi si chiama qualsiasi setter facoltativo dopo:

Pizza pizza = new Pizza(12);
pizza.setCheese(true);
pizza.setPepperoni(true);
pizza.setBacon(true);

Il problema qui è che, poiché l'oggetto viene creato su più chiamate, potrebbe trovarsi in uno stato incoerente durante la sua costruzione. Ciò richiede anche molti sforzi in più per garantire la sicurezza del thread.

La migliore alternativa è utilizzare il Builder Pattern.

public class Pizza {
  private int size;
  private boolean cheese;
  private boolean pepperoni;
  private boolean bacon;

  public static class Builder {
    //required
    private final int size;

    //optional
    private boolean cheese = false;
    private boolean pepperoni = false;
    private boolean bacon = false;

    public Builder(int size) {
      this.size = size;
    }

    public Builder cheese(boolean value) {
      cheese = value;
      return this;
    }

    public Builder pepperoni(boolean value) {
      pepperoni = value;
      return this;
    }

    public Builder bacon(boolean value) {
      bacon = value;
      return this;
    }

    public Pizza build() {
      return new Pizza(this);
    }
  }

  private Pizza(Builder builder) {
    size = builder.size;
    cheese = builder.cheese;
    pepperoni = builder.pepperoni;
    bacon = builder.bacon;
  }
}

Nota che la pizza è immutabile e che i valori dei parametri sono tutti in un'unica posizione . Poiché i metodi setter del Builder restituiscono l'oggetto Builder, sono in grado di essere concatenati .

Pizza pizza = new Pizza.Builder(12)
                       .cheese(true)
                       .pepperoni(true)
                       .bacon(true)
                       .build();

Ciò si traduce in un codice facile da scrivere e molto facile da leggere e comprendere. In questo esempio, il metodo di generazione potrebbe essere modificato per verificare i parametri dopo averli è stato copiato dal builder nell'oggetto Pizza e genera un'eccezione IllegalStateException se è stato fornito un valore di parametro non valido. Questo modello è flessibile ed è facile aggiungere ulteriori parametri in futuro. È davvero utile solo se hai più di 4 o 5 parametri per un costruttore. Detto questo, potrebbe essere utile in primo luogo se sospetti di poter aggiungere altri parametri in futuro.

Ho preso molto in prestito questo argomento dal libro Effective Java, 2nd Edition di Joshua Bloch. Per saperne di più su questo modello e altre pratiche Java efficaci Lo consiglio vivamente.

Prendi in considerazione un ristorante. La creazione del "pasto di oggi" è un modello di fabbrica, perché dici alla cucina "prendimi il pasto di oggi" e la cucina (fabbrica) decide quale oggetto generare, in base a criteri nascosti.

Il builder appare se ordini una pizza personalizzata. In questo caso, il cameriere dice allo chef (costruttore) " ho bisogno di una pizza; aggiungi formaggio, cipolle e pancetta! " Pertanto, il builder espone gli attributi che l'oggetto generato dovrebbe avere, ma nasconde come impostarli.

La classe .NET StringBuilder è un ottimo esempio di modello di generatore. Viene utilizzato principalmente per creare una stringa in una serie di passaggi. Il risultato finale che si ottiene facendo ToString () è sempre una stringa ma la creazione di quella stringa varia in base alle funzioni utilizzate nella classe StringBuilder. Per riassumere, l'idea di base è costruire oggetti complessi e nascondere i dettagli di implementazione di come viene costruito.

Per un problema multi-thread, avevamo bisogno di costruire un oggetto complesso per ogni thread. L'oggetto rappresentava i dati in elaborazione e potrebbe cambiare in base all'input dell'utente.

Potremmo usare una fabbrica invece? Sì

Perché non l'abbiamo fatto? Il costruttore ha più senso immagino.

Le fabbriche vengono utilizzate per creare diversi tipi di oggetti dello stesso tipo di base (implementare la stessa interfaccia o classe di base).

I costruttori costruiscono ripetutamente lo stesso tipo di oggetto, ma la costruzione è dinamica, quindi può essere cambiata in fase di esecuzione.

Mentre attraversavo il framework Microsoft MVC, ho pensato al modello di generatore. Mi sono imbattuto nel modello nella classe ControllerBuilder. Questa classe restituisce la classe factory del controller, che viene quindi utilizzata per costruire un controller concreto.

Il vantaggio che vedo nell'uso del modello builder è che puoi creare una tua fabbrica e collegarla al framework.

@Tetha, può esserci un ristorante (Framework) gestito da un ragazzo italiano che serve pizza. Per preparare la pizza un ragazzo italiano (Object Builder) usa Owen (Factory) con una base per pizza (classe base).

Ora un ragazzo indiano prende il ristorante da un ragazzo italiano. Server indiano (Framework) server dosa invece di pizza. Per preparare il ragazzo indiano dosa (costruttore di oggetti) usa la padella (fabbrica) con un Maida (classe base)

Se guardi lo scenario, il cibo è diverso, il modo in cui il cibo è preparato è diverso, ma nello stesso ristorante (sotto lo stesso quadro). Il ristorante dovrebbe essere costruito in modo tale da supportare cinese, messicano o qualsiasi cucina. Object builder all'interno di framework facilita il plug-in del tipo di cucina che desideri. per esempio

class RestaurantObjectBuilder
{
   IFactory _factory = new DefaultFoodFactory();

   //This can be used when you want to plugin the 
   public void SetFoodFactory(IFactory customFactory)
   {
        _factory = customFactory;
   }

   public IFactory GetFoodFactory()
   {
      return _factory;
   }
}

Lo usi quando hai molte opzioni da affrontare. Pensa a cose come jmock:

m.expects(once())
    .method("testMethod")
    .with(eq(1), eq(2))
    .returns("someResponse");

Sembra molto più naturale ed è ... possibile.

Esistono anche xml building, string building e molte altre cose. Immagina se java.util.Map fosse stato creato come builder. Potresti fare cose del genere:

Map<String, Integer> m = new HashMap<String, Integer>()
    .put("a", 1)
    .put("b", 2)
    .put("c", 3);

Partendo dalle risposte precedenti (gioco di parole), un eccellente esempio del mondo reale è Groovy supporto integrato per Builders .

Vedi Builders nella Documentazione Groovy

Un altro vantaggio del costruttore è che se hai una Factory, c'è ancora qualche accoppiamento nel tuo codice, perché per far funzionare la Factory, deve conoscere tutti gli oggetti che può eventualmente creare . Se aggiungi un altro oggetto che potrebbe essere creato, dovrai modificare la classe di fabbrica per includerlo. Questo succede anche nella Fabbrica astratta.

Con il costruttore, invece, devi solo creare un nuovo costruttore concreto per questa nuova classe. La classe director rimarrà la stessa, perché riceve il builder nel costruttore.

Inoltre, ci sono molti gusti di builder. Kamikaze Mercenary`s ne dà un altro.

/// <summary>
/// Builder
/// </summary>
public interface IWebRequestBuilder
{
    IWebRequestBuilder BuildHost(string host);

    IWebRequestBuilder BuildPort(int port);

    IWebRequestBuilder BuildPath(string path);

    IWebRequestBuilder BuildQuery(string query);

    IWebRequestBuilder BuildScheme(string scheme);

    IWebRequestBuilder BuildTimeout(int timeout);

    WebRequest Build();
}

/// <summary>
/// ConcreteBuilder #1
/// </summary>
public class HttpWebRequestBuilder : IWebRequestBuilder
{
    private string _host;

    private string _path = string.Empty;

    private string _query = string.Empty;

    private string _scheme = "http";

    private int _port = 80;

    private int _timeout = -1;

    public IWebRequestBuilder BuildHost(string host)
    {
        _host = host;
        return this;
    }

    public IWebRequestBuilder BuildPort(int port)
    {
        _port = port;
        return this;
    }

    public IWebRequestBuilder BuildPath(string path)
    {
        _path = path;
        return this;
    }

    public IWebRequestBuilder BuildQuery(string query)
    {
        _query = query;
        return this;
    }

    public IWebRequestBuilder BuildScheme(string scheme)
    {
        _scheme = scheme;
        return this;
    }

    public IWebRequestBuilder BuildTimeout(int timeout)
    {
        _timeout = timeout;
        return this;
    }

    protected virtual void BeforeBuild(HttpWebRequest httpWebRequest) {
    }

    public WebRequest Build()
    {
        var uri = _scheme + "://" + _host + ":" + _port + "/" + _path + "?" + _query;

        var httpWebRequest = WebRequest.CreateHttp(uri);

        httpWebRequest.Timeout = _timeout;

        BeforeBuild(httpWebRequest);

        return httpWebRequest;
    }
}

/// <summary>
/// ConcreteBuilder #2
/// </summary>
public class ProxyHttpWebRequestBuilder : HttpWebRequestBuilder
{
    private string _proxy = null;

    public ProxyHttpWebRequestBuilder(string proxy)
    {
        _proxy = proxy;
    }

    protected override void BeforeBuild(HttpWebRequest httpWebRequest)
    {
        httpWebRequest.Proxy = new WebProxy(_proxy);
    }
}

/// <summary>
/// Director
/// </summary>
public class SearchRequest
{

    private IWebRequestBuilder _requestBuilder;

    public SearchRequest(IWebRequestBuilder requestBuilder)
    {
        _requestBuilder = requestBuilder;
    }

    public WebRequest Construct(string searchQuery)
    {
        return _requestBuilder
        .BuildHost("ajax.googleapis.com")
        .BuildPort(80)
        .BuildPath("ajax/services/search/web")
        .BuildQuery("v=1.0&q=" + HttpUtility.UrlEncode(searchQuery))
        .BuildScheme("http")
        .BuildTimeout(-1)
        .Build();
    }

    public string GetResults(string searchQuery) {
        var request = Construct(searchQuery);
        var resp = request.GetResponse();

        using (StreamReader stream = new StreamReader(resp.GetResponseStream()))
        {
            return stream.ReadToEnd();
        }
    }
}

class Program
{
    /// <summary>
    /// Inside both requests the same SearchRequest.Construct(string) method is used.
    /// But finally different HttpWebRequest objects are built.
    /// </summary>
    static void Main(string[] args)
    {
        var request1 = new SearchRequest(new HttpWebRequestBuilder());
        var results1 = request1.GetResults("IBM");
        Console.WriteLine(results1);

        var request2 = new SearchRequest(new ProxyHttpWebRequestBuilder("localhost:80"));
        var results2 = request2.GetResults("IBM");
        Console.WriteLine(results2);
    }
}

Non ho sempre apprezzato il modello Builder come qualcosa di ingombrante, invadente e molto spesso maltrattato da programmatori meno esperti. È un modello che ha senso solo se è necessario assemblare l'oggetto da alcuni dati che richiedono una fase di post-inizializzazione (vale a dire, una volta raccolti tutti i dati, fare qualcosa con esso). Al contrario, nel 99% dei casi i costruttori vengono semplicemente utilizzati per inizializzare i membri della classe.

In questi casi è molto meglio dichiarare semplicemente conXyz (...) all'interno della classe e farli restituire un riferimento a se stesso.

Considera questo:

public class Complex {

    private String first;
    private String second;
    private String third;

    public String getFirst(){
       return first; 
    }

    public void setFirst(String first){
       this.first=first; 
    }

    ... 

    public Complex withFirst(String first){
       this.first=first;
       return this; 
    }

    public Complex withSecond(String second){
       this.second=second;
       return this; 
    }

    public Complex withThird(String third){
       this.third=third;
       return this; 
    }

}


Complex complex = new Complex()
     .withFirst("first value")
     .withSecond("second value")
     .withThird("third value");

Ora abbiamo una singola classe ordinata che gestisce la propria inizializzazione e fa praticamente lo stesso lavoro del costruttore, tranne che è molto più elegante.

Ho usato builder nella libreria di messaggistica di casa. Il core della libreria stava ricevendo i dati dal filo, raccogliendoli con l'istanza di Builder, quindi, una volta che Builder aveva deciso di avere tutto il necessario per creare un'istanza di messaggio, Builder.GetMessage () stava costruendo un'istanza di messaggio utilizzando i dati raccolti dal filo.

Quando volevo usare lo standard XMLGregorianCalendar per il mio XML per obiettare al marshalling di DateTime in Java, ho sentito molti commenti su quanto fosse pesante e ingombrante usarlo. Stavo cercando di controllare i campi XML in xs: strutture datetime per gestire fuso orario, millisecondi, ecc.

Quindi ho progettato un'utilità per costruire un calendario XMLGregorian da un GregorianCalendar o java.util.Date.

A causa del luogo in cui lavoro, non sono autorizzato a condividerlo online senza restrizioni legali, ma ecco un esempio di come un client lo utilizza. Estrae i dettagli e filtra alcune delle implementazioni di XMLGregorianCalendar che sono meno utilizzate per xs: datetime.

XMLGregorianCalendarBuilder builder = XMLGregorianCalendarBuilder.newInstance(jdkDate);
XMLGregorianCalendar xmlCalendar = builder.excludeMillis().excludeOffset().build();

Concesso questo modello è più di un filtro in quanto imposta i campi in xmlCalendar come indefinito in modo che siano esclusi, tuttavia "costruisce " esso. Ho facilmente aggiunto altre opzioni al builder per creare una struttura xs: date e xs: time e anche per manipolare gli offset del fuso orario quando necessario.

Se hai mai visto il codice che crea e usa XMLGregorianCalendar, vedresti come questo ha reso molto più facile la manipolazione.

Dai un'occhiata a InnerBuilder, un plug-in IntelliJ IDEA che aggiunge un'azione 'Builder' al menu Genera (Alt + Inserisci) che genera una classe builder interna come descritto in Effective Java

https://github.com/analytically/innerbuilder

Un ottimo esempio del mondo reale è quello di utilizzare quando si verificano unità di test delle classi. Usi i costruttori sut (System Under Test).

Esempio:

Classe:

public class CustomAuthenticationService
{
    private ICloudService _cloudService;
    private IDatabaseService _databaseService;

    public CustomAuthenticationService(ICloudService cloudService, IDatabaseService databaseService)
    {
        _cloudService = cloudService;
        _databaseService = databaseService;
    }

    public bool IsAuthorized(User user)
    {            
        //Implementation Details
        return true;

}

Prova:

    [Test]
    public void Given_a_User_With_Permission_When_Verifying_If_Authorized_Then_Authorize_It_Returning_True()
    {
        CustomAuthenticationService sut = new CustomAuthenticationServiceBuilder();
        User userWithAuthorization = null;

        var result = sut.IsAuthorized(userWithAuthorization);

        Assert.That(result, Is.True);
    }

sut Builder:

public class CustomAuthenticationServiceBuilder
{
    private ICloudService _cloudService;
    private IDatabaseService _databaseService;

    public CustomAuthenticationServiceBuilder()
    {
        _cloudService = new AwsService();
        _databaseService = new SqlServerService();
    }

    public CustomAuthenticationServiceBuilder WithAzureService(AzureService azureService)
    {
        _cloudService = azureService;

        return this;
    }

    public CustomAuthenticationServiceBuilder WithOracleService(OracleService oracleService)
    {
        _databaseService = oracleService;

        return this;
    }

    public CustomAuthenticationService Build()
    {
        return new CustomAuthenticationService(_cloudService, _databaseService);
    }

    public static implicit operator CustomAuthenticationService (CustomAuthenticationServiceBuilder builder)
    {
        return builder.Build();
    }
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top