risposta-alla-domanda-sullo-sviluppo-web-bd.com

Quando useresti il ​​Pattern Builder?

Quali sono alcuni common , esempi del mondo reale di utilizzo del Builder Pattern? Cosa ti compra? Perché non usare solo un modello di fabbrica?

488
Charles Graham

La differenza fondamentale tra un costruttore e un IMHO di fabbrica è che un costruttore è utile quando devi 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 fabbrica viene utilizzata quando la fabbrica 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 la costruzione di frammenti HTML, ad esempio potrei avere un Builder per costruire un tipo specifico di tabella e potrebbe avere i seguenti metodi (i parametri non sono mostrati) :

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

Questo builder quindi sputerebbe fuori l'HTML per me. Questo è molto più facile da leggere rispetto a un metodo procedurale di grandi dimensioni.

Check out Builder Pattern su Wikipedia .

242
JoshBerke

Di seguito sono riportati alcuni motivi per sostenere l'uso del pattern e del codice di esempio in Java, ma è un'implementazione del Pattern Builder coperto dalla Gang of Four in Design Patterns . I motivi per cui lo useresti in Java sono applicabili anche ad altri linguaggi di programmazione.

Come afferma Joshua Bloch in Effective Java, 2nd Edition :

Il modello di 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 abbiamo tutti 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 Pattern Costruttore Telescopico. Il problema con questo modello è che quando i costruttori sono lunghi 4 o 5 parametri diventa difficile da ricordare l'ordine richiesto dei parametri e il particolare costruttore che si desidera in una determinata situazione.

Un'alternativa alternativa al modello Telescoping Constructor è il JavaBean Pattern dove si chiama un costruttore con i parametri obbligatori e quindi si chiama qualsiasi setter opzionali 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 un notevole sforzo supplementare per garantire la sicurezza dei thread.

L'alternativa migliore è usare il Pattern Builder.

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 che sono in grado di essere incatenato .

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

Questo risulta in un codice che è facile da scrivere e molto facile da leggere e comprendere. In questo esempio, il metodo di costruzione potrebbe essere modificato per controllare i parametri dopo che sono stati copiati dal builder sull'oggetto Pizza e lanciare un IllegalStateException se è stato fornito un valore di parametro non valido. Questo modello è flessibile ed è facile aggiungere ulteriori parametri ad esso in futuro. È davvero utile solo se si hanno più di 4 o 5 parametri per un costruttore. Detto questo, potrebbe essere utile in primo luogo se si sospetta che si possano aggiungere ulteriori 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 su altre pratiche Java efficaci lo consiglio vivamente.

961
Aaron

Considera un ristorante. La creazione del "pasto di oggi" è un modello di fabbrica, perché dici alla cucina "prendi 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, aggiungo formaggio, cipolle e pancetta ad esso!" Pertanto, il builder espone gli attributi che l'oggetto generato dovrebbe avere, ma nasconde come impostarli.

309
Tetha

La classe .NET StringBuilder è un ottimo esempio di pattern builder. Viene principalmente utilizzato 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 a seconda di quali funzioni sono state utilizzate nella classe StringBuilder. Per riassumere, l'idea di base è quella di costruire oggetti complessi e nascondere i dettagli di implementazione di come viene costruito.

18
dssfsdf

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

Potremmo usare una fabbrica invece? sì

Perché no? Il costruttore ha più senso, credo.

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

I costruttori costruiscono lo stesso tipo di oggetto più e più volte, ma la costruzione è dinamica e può essere modificata in fase di runtime.

10

Mentre passavo attraverso il framework Microsoft MVC, ho preso in considerazione il modello di builder. Mi sono imbattuto nel modello nella classe ControllerBuilder. Questa classe restituisce la classe factory del controller, che viene quindi utilizzata per creare un controller concreto.

Il vantaggio che vedo usando il pattern builder è che puoi creare una fabbrica e collegarla al framework.

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

Ora il tipo indiano prende il ristorante da un ragazzo italiano. Ristorante indiano (quadro) server dosa anziché pizza. Per preparare il dosa indiano (costruttore di oggetti) usa Frying Pan (Factory) con una Maida (classe base)

Se si guarda allo scenario, il cibo è diverso, il modo in cui il cibo viene preparato è diverso, ma nello stesso ristorante (sotto lo stesso quadro). Il ristorante dovrebbe essere costruito in modo tale da supportare cinesi, messicani o qualsiasi altra cucina. Il generatore di oggetti all'interno del framework facilita l'estensione 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;
   }
}
9
Nitin

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.

C'è anche xml building, string building e molte altre cose. Immagina se Java.util.Map fosse stato messo come builder. Potresti fare cose del genere:

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

Basandosi sulle risposte precedenti (gioco di parole), un eccellente esempio del mondo reale è Groovy è integrato nel supporto per Buildersname__.

Vedi Builders nella Groovy Documentation

6
Ken Gentle

Non mi è mai piaciuto il pattern Builder come qualcosa di poco pratico, invadente e molto spesso abusato da programmatori meno esperti. È un pattern che ha senso solo se è necessario assemblare l'oggetto da alcuni dati che richiedono un passaggio post-inizializzazione (ovvero una volta raccolti tutti i dati - fai qualcosa con esso). Invece, nel 99% dei costruttori di tempo vengono semplicemente usati per inizializzare i membri della classe.

In questi casi è molto meglio dichiarare semplicemente i setter di tipo withXyz(...) 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 pulita che gestisce la propria inizializzazione e fa praticamente lo stesso lavoro del costruttore, tranne che è molto più elegante.

5
Pavel Lechev
/// <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);
    }
}
5
Raman Zhylich

Un altro vantaggio del builder è 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ò creare . Se aggiungi un altro oggetto che potrebbe essere creato, dovrai modificare la classe factory per includerlo. Questo succede anche nella Fabbrica Astratta.

Con il costruttore, d'altra parte, devi solo creare un nuovo costruttore di calcestruzzo per questa nuova classe. La classe director rimarrà la stessa, perché riceve il costruttore nel costruttore.

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

5
Lino Rosa

Ho usato il builder nella libreria di messaggistica locale. Il nucleo della libreria stava ricevendo i dati dal filo, raccogliendolo con l'istanza di Builder, quindi, una volta che Builder decise di avere tutto il necessario per creare un'istanza di Message, Builder.GetMessage () stava costruendo un'istanza di messaggio usando i dati raccolti dal filo.

3
wasker

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

Così ho progettato un'utilità per creare un calendario XMLGregorian da un GregorianCalendar o Java.util.Date.

A causa del luogo in cui lavoro, non sono autorizzato a condividerlo online senza essere autorizzato, 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();

Assegnato a questo modello è più di un filtro in quanto imposta i campi in xmlCalendar come non definito in modo che siano esclusi, ma "lo costruisce" ancora. Ho facilmente aggiunto altre opzioni al builder per creare una struttura xs: date e xs: time e anche per modificare gli offset del fuso orario quando necessario.

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

2
John Brown

Scopri InnerBuilder, un plug-in IntelliJ IDEA che aggiunge un'azione 'Builder' al menu Genera (Alt + Insert) che genera una classe builder interna come descritto in Java efficace

https://github.com/analytically/innerbuilder

2
analytically

Un ottimo esempio del mondo reale è da utilizzare quando un'unità testa le tue lezioni. 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;

}

Test:

    [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();
    }
}
0
Rafael Miceli