Quando você usaria o Padrão do Construtor? [fechadas]

531

Quais são alguns dos comuns , exemplos do mundo real de usar o padrão Builder? O que isso te compra? Por que não usar apenas um padrão de fábrica?

Charles Graham
fonte
O stackoverflow.com/questions/35238292/… mencionou algumas APIs que usam padrão de construtor
Alireza Fattahi
As respostas de Aaron e Tetha são realmente informativas. Aqui está o artigo completo relacionado a essas respostas.
Diablo

Respostas:

262

A principal diferença entre um construtor e um IMHO de fábrica é que um construtor é útil quando você precisa fazer muitas coisas para construir um objeto. Por exemplo, imagine um DOM. Você precisa criar muitos nós e atributos para obter seu objeto final. Uma fábrica é usada quando a fábrica pode criar facilmente o objeto inteiro em uma chamada de método.

Um exemplo de uso de um construtor é a construção de um documento XML; usei esse modelo ao criar fragmentos HTML; por exemplo, posso ter um construtor para criar um tipo específico de tabela e pode ter os seguintes métodos (os parâmetros não são mostrados) :

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

Esse construtor cuspiria o HTML para mim. Isso é muito mais fácil de ler do que percorrer um grande método processual.

Confira o Builder Pattern na Wikipedia .

JoshBerke
fonte
1021

Abaixo estão alguns motivos para argumentar sobre o uso do padrão e código de exemplo em Java, mas é uma implementação do Padrão do Construtor coberto pela Gang of Four nos Design Patterns . Os motivos pelos quais você o usaria em Java também são aplicáveis ​​a outras linguagens de programação.

Como Joshua Bloch afirma em Java Efetivo, 2ª Edição :

O padrão do construtor é uma boa opção ao projetar classes cujos construtores ou fábricas estáticas teriam mais do que alguns parâmetros.

Em algum momento, todos encontramos uma classe com uma lista de construtores em que cada adição adiciona um novo parâmetro de opção:

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

Isso é chamado de Padrão do Construtor Telescópico. O problema com esse padrão é que, uma vez que os construtores têm 4 ou 5 parâmetros, fica difícil lembrar a ordem necessária dos parâmetros , bem como qual construtor específico você deseja em uma determinada situação.

Uma alternativa que você tem para o Telescoping Constructor Pattern é o JavaBean Pattern, no qual você chama um construtor com os parâmetros obrigatórios e, em seguida, chama os setters opcionais após:

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

O problema aqui é que, como o objeto é criado por várias chamadas, ele pode estar em um estado inconsistente no meio de sua construção. Isso também requer muito esforço extra para garantir a segurança do encadeamento.

A melhor alternativa é usar o Padrão do Construtor.

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;
  }
}

Observe que Pizza é imutável e que os valores dos parâmetros estão todos em um único local . Como os métodos setter do Builder retornam o objeto Builder, eles podem ser encadeados .

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

Isso resulta em um código fácil de escrever e muito fácil de ler e entender.Neste exemplo, o método de construção pode ser modificado para verificar os parâmetros após serem copiados do construtor para o objeto Pizza e lançar uma IllegalStateException se um valor de parâmetro inválido tiver sido fornecido. Esse padrão é flexível e é fácil adicionar mais parâmetros no futuro. É realmente útil apenas se você tiver mais de 4 ou 5 parâmetros para um construtor. Dito isto, pode valer a pena em primeiro lugar, se você suspeitar que esteja adicionando mais parâmetros no futuro.

Eu emprestei muito esse tópico do livro Effective Java, 2nd Edition, de Joshua Bloch. Para saber mais sobre esse padrão e outras práticas Java eficazes, eu o recomendo.

Aaron
fonte
24
Diferente do construtor GOF original, certo? Porque não há classe de diretor. Parece quase outro padrão para mim, mas eu concordo que é muito útil.
Lino Rosa
194
Para este exemplo particular, não seria mais agradável para remover os parâmetros booleanos e ser capaz de dizernew Pizza.Builder(12).cheese().pepperoni().bacon().build();
Fabian Steeg
46
Parece mais uma interface fluente do que um padrão de construtor .
22812 Robert Harvey
21
@Fabian Steeg, acho que as pessoas estão exagerando nos setters booleanos de aparência mais agradável, lembre-se de que esse tipo de setter não permite alterações no tempo de execução: Pizza.Builder(12).cheese().pepperoni().bacon().build();você precisaria recompilar seu código ou ter uma lógica desnecessária se precisar apenas de pepperoni pizzas. No mínimo, você também deve fornecer versões parametrizadas, como o @Kamikaze Mercenary originalmente sugerido. Pizza.Builder(12).cheese(true).pepperoni(false).bacon(false).build();. Por outro lado, nunca testamos a unidade, não é?
precisa saber é o seguinte
23
@JasonC Certo, e para que serve uma pizza imutável?
Maarten Bodewes
325

Considere um restaurante. A criação da "refeição de hoje" é um padrão de fábrica, porque você diz à cozinha "leve-me a refeição de hoje" e a cozinha (fábrica) decide qual objeto gerar, com base em critérios ocultos.

O construtor será exibido se você solicitar uma pizza personalizada. Nesse caso, o garçom diz ao chef (construtor): "Eu preciso de uma pizza; adicione queijo, cebola e bacon a ela!" Portanto, o construtor expõe os atributos que o objeto gerado deve ter, mas oculta como configurá-los.

Tetha
fonte
Nitin estendeu a analogia da cozinha em outra resposta a essa pergunta .
Pops
19

A classe .NET StringBuilder é um ótimo exemplo de padrão de construtor. É usado principalmente para criar uma string em uma série de etapas. O resultado final que você faz ToString () é sempre uma string, mas a criação dessa string varia de acordo com as funções da classe StringBuilder que foram usadas. Em resumo, a idéia básica é construir objetos complexos e ocultar os detalhes de implementação de como está sendo construído.


fonte
9
Eu não acho que esse seja o padrão do construtor. O StringBuilder é apenas mais uma implementação de uma classe de array de caracteres (isto é, string), mas leva em consideração o gerenciamento de desempenho e memória, porque as strings são imutáveis.
Charles Graham
23
É absolutamente o padrão do construtor, assim como a classe StringBuilder em Java. Observe como o método append () de ambas as classes retorna o próprio StringBuilder, para que você possa encadear b.append(...).append(...)antes de finalmente chamar toString(). Citação: infoq.com/articles/internal-dsls-java
pohl
3
@pohl Ya: Eu não acho que isso seja realmente um padrão de construtor, diria que é mais apenas uma interface fluente.
Didier A.
"Observe como o método append () de ambas as classes retorna o próprio StringBuilder", esse não é o padrão do Builder, é apenas uma interface fluente. É frequente que um construtor também use uma interface fluente. Um construtor não precisa ter uma interface fluente.
22816 bydtev
Mas observe que o StringBuilder por natureza não está sincronizado, ao contrário do StringBuffer que está sincronizado.
Alan Profundo
11

Para um problema multithread, precisamos de um objeto complexo para ser construído para cada thread. O objeto representou os dados que estão sendo processados ​​e pode mudar dependendo da entrada do usuário.

Poderíamos usar uma fábrica? sim

Por que nós não? Construtor faz mais sentido, eu acho.

As fábricas são usadas para criar diferentes tipos de objetos que são do mesmo tipo básico (implementam a mesma interface ou classe base).

Os construtores constroem o mesmo tipo de objeto repetidamente, mas a construção é dinâmica, portanto pode ser alterada em tempo de execução.

Cameron MacFarland
fonte
9

Você o usa quando tem muitas opções para lidar. Pense em coisas como jmock:

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

Parece muito mais natural e é ... possível.

Há também a construção de XML, a construção de strings e muitas outras coisas. Imagine se java.util.Maptivesse colocado como construtor. Você poderia fazer coisas assim:

Map<String, Integer> m = new HashMap<String, Integer>()
    .put("a", 1)
    .put("b", 2)
    .put("c", 3);
Dustin
fonte
3
Esqueci-me de ler o "se" Mapa implementou um padrão do construtor e foi surpresos de ver a construção lá .. :)
Sethu
3
:) Me desculpe por isso. É comum em muitos idiomas retornar a si próprio em vez de nulo. Você pode fazer isso em java, mas não é muito comum.
Dustin
7
O exemplo do mapa é simplesmente um exemplo de encadeamento de métodos.
nogridbag
@nogridbag Na verdade, seria mais próximo do método em cascata. Embora esteja usando encadeamento de maneira a simular cascata, obviamente está encadeando, mas semanticamente se comporta como cascata.
Didier A.
9

Ao passar pela estrutura do Microsoft MVC, pensei em um padrão de construtor. Me deparei com o padrão na classe ControllerBuilder. Esta classe é para retornar a classe de fábrica do controlador, que é usada para construir o controlador de concreto.

A vantagem que vejo ao usar o padrão do construtor é que você pode criar sua própria fábrica e conectá-la à estrutura.

@Tetha, pode haver um restaurante (Framework) administrado por um italiano, que serve Pizza. Para preparar uma pizza, o italiano (Object Builder) usa Owen (Factory) com uma base de pizza (classe base).

Agora, o indiano assume o restaurante do italiano. Servidores de restaurante indiano (Framework) dosa em vez de pizza. Para preparar a dosa, o índio (construtor de objetos) usa a Frigideira (Fábrica) com um Maida (classe base)

Se você observar o cenário, a comida é diferente, a maneira como a comida é preparada é diferente, mas no mesmo restaurante (sob a mesma estrutura). O restaurante deve ser construído de forma a poder apoiar cozinha chinesa, mexicana ou qualquer outra cozinha. O construtor de objetos dentro da estrutura facilita o plug-in do tipo de cozinha que você deseja. por exemplo

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;
   }
}
Nitin
fonte
7

Eu sempre não gostei do padrão Builder como algo pesado, intrusivo e muitas vezes abusado por programadores menos experientes. É um padrão que só faz sentido se você precisar montar o objeto a partir de alguns dados que exijam uma etapa de pós-inicialização (ou seja, depois que todos os dados forem coletados - faça algo com ele). Em vez disso, em 99% do tempo, os construtores são simplesmente usados ​​para inicializar os alunos.

Nesses casos, é muito melhor simplesmente declarar withXyz(...) criadores de tipos dentro da classe e fazê-los retornar uma referência a si mesma.

Considere isto:

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");

Agora temos uma classe única organizada que gerencia sua própria inicialização e faz praticamente o mesmo trabalho que o construtor, exceto que é muito mais elegante.

Pavel Lechev
fonte
Acabei de decidir que queria criar meu complexo documento XML como JSON. Em primeiro lugar, como sei que a classe 'Complex' é capaz de fornecer um produto XMLable em primeiro lugar e como eu a alteraria para produzir um objeto JSONable? Resposta rápida: não posso porque preciso usar construtores. E chegamos ao ponto ...
David Barker
1
bs totais, Builder é projetado para construir objetos imutáveis e com a capacidade mudar a maneira de construir, no futuro, sem tocar a classe de produto
Mickey Tin
6
hmmm? Você leu em algum lugar na resposta me dizendo para que o Builder foi projetado? É um ponto de vista alternativo à pergunta no topo "Quando você usaria o Padrão do Construtor?", Que se baseia na experiência de incontáveis ​​abusos do padrão, onde algo muito mais simples faz o trabalho melhor. Todos os padrões são úteis se você souber quando e como usá-los - esse é o objetivo de documentar os padrões em primeiro lugar! Quando o padrão é usado em excesso ou pior - mal utilizado -, torna-se um anti-padrão no contexto do seu código. Infelizmente ...
Pavel Lechev
6

Outra vantagem do construtor é que, se você tiver um Factory, ainda há algum acoplamento no seu código, porque para o Factory funcionar, ele precisa conhecer todos os objetos que ele pode criar. . Se você adicionar outro objeto que possa ser criado, precisará modificar a classe de fábrica para incluí-lo. Isso também acontece na Abstract Factory.

Com o construtor, por outro lado, você apenas precisa criar um novo construtor de concreto para essa nova classe. A classe diretor permanecerá a mesma, porque recebe o construtor no construtor.

Além disso, existem muitos sabores do construtor. O Kamikaze Mercenary`s dá outro.

Lino Rosa
fonte
6
/// <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);
    }
}
Raman Zhylich
fonte
1
Você pode melhorar suas respostas de duas maneiras: 1) Faça SSCCE. 2) Explique como isso responde à pergunta.
James.garriss
3

Eu usei o construtor na biblioteca de mensagens criada em casa. O núcleo da biblioteca estava recebendo dados da ligação, coletando-os com a instância do Builder e, assim que o Builder decidiu que tinha tudo o que era necessário para criar uma instância de Message, o Builder.GetMessage () estava construindo uma instância de mensagem usando os dados coletados do fio.

lavador
fonte
3

Confira o InnerBuilder, um plug-in do IntelliJ IDEA que adiciona uma ação 'Builder' ao menu Generate (Alt + Insert), que gera uma classe interna do construtor, conforme descrito em Java Efetivo

https://github.com/analytically/innerbuilder

analiticamente
fonte
2

Quando eu queria usar o XMLGregorianCalendar padrão para o meu XML objetar a organização do DateTime em Java, ouvi muitos comentários sobre o peso e a complexidade do uso dele. Eu estava tentando controlar os campos XML nas estruturas xs: datetime para gerenciar fuso horário, milissegundos etc.

Por isso, projetei um utilitário para criar um calendário XMLGregorian a partir de um GregorianCalendar ou java.util.Date.

Por causa de onde trabalho, não tenho permissão para compartilhá-lo online sem legal, mas aqui está um exemplo de como um cliente o usa. Ele abstrai os detalhes e filtra parte da implementação do XMLGregorianCalendar que é menos usada para xs: datetime.

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

Concedido que esse padrão é mais um filtro, pois define os campos no xmlCalendar como indefinidos para que sejam excluídos, ele ainda o "constrói". Adicionei facilmente outras opções ao construtor para criar uma estrutura xs: date e xs: time e também para manipular compensações de fuso horário quando necessário.

Se você já viu um código que cria e usa XMLGregorianCalendar, verá como isso facilitou muito a manipulação.

John Brown
fonte
0

Um ótimo exemplo do mundo real é usar ao testar suas classes. Você usa construtores sut (System Under Test).

Exemplo:

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;

}

Teste:

    [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();
    }
}
Rafael Miceli
fonte
Por que você precisaria de um construtor nesse caso, em vez de apenas adicionar setters à sua CustomAuthenticationServiceclasse?
Laur Ivan
Essa é uma boa pergunta @LaurIvan! Talvez meu exemplo tenha sido um pouco ruim, mas imagine que você não possa alterar a classe CustomAuthenticationService, o construtor seria uma maneira atraente de melhorar a leitura dos testes de unidade. E eu acho que criar getters e setters irão expor seus campos, que serão usados ​​apenas para seus testes. Se você quiser agora mais sobre o Sut Builder, pode ler sobre o Test Data Builder , que é praticamente o mesmo, exceto para suts.
Rafael Miceli