¿Cuándo usarías el Patrón del Constructor? [cerrado]


¿Cuáles son algunos comunes, ejemplos del mundo real de usar el Patrón de Constructor? ¿Qué te compra? ¿Por qué no usar un Patrón de Fábrica?

Author: Flow, 2008-11-30

15 answers

La diferencia clave entre un builder y factory IMHO, es que un builder es útil cuando necesitas hacer muchas cosas para construir un objeto. Por ejemplo, imagine un DOM. Tienes que crear muchos nodos y atributos para obtener tu objeto final. Una fábrica se utiliza cuando la fábrica puede crear fácilmente todo el objeto dentro de una llamada a método.

Un ejemplo de uso de un constructor es construir un documento XML, he utilizado este modelo al construir fragmentos HTML, por ejemplo, podría tener un Builder para construir un tipo específico de tabla y puede tener los siguientes métodos (los parámetros no se muestran):

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

Este constructor entonces escupiría el HTML para mí. Esto es mucho más fácil de leer que caminar a través de un método de procedimiento grande.

Echa un vistazo a Patrón Constructor en Wikipedia.

 230
Author: JoshBerke,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-09-03 21:24:30

A continuación se presentan algunas razones para el uso del patrón y el código de ejemplo en Java, pero es una implementación del Patrón de Constructor cubierto por la Pandilla de Cuatro en Patrones de diseño. Las razones por las que lo usarías en Java también son aplicables a otros lenguajes de programación.

Como Joshua Bloch afirma en Java Efectivo, 2a Edición :

El patrón de constructor es una buena opción al diseñar clases cuyos constructores o fábricas estáticas tendría más de un puñado de parámetros.

Todos hemos encontrado en algún momento una clase con una lista de constructores donde cada adición agrega un nuevo parámetro de opción:

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

Esto se llama el Patrón Constructor Telescópico. El problema con este patrón es que una vez que los constructores tienen 4 o 5 parámetros de largo, se vuelve difícil de recordar el orden requerido de los parámetros , así como qué constructor en particular en una situación dada.

Una alternativa que tienes al Patrón Constructor Telescópico es el Patrón JavaBean donde llamas a un constructor con los parámetros obligatorios y luego llamas a cualquier configurador opcional después de:

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

El problema aquí es que debido a que el objeto se crea sobre varias llamadas, puede estar en un estado inconsistente a mitad de su construcción. Esto también requiere mucho esfuerzo adicional para garantizar el hilo seguridad.

La mejor alternativa es utilizar el Patrón Constructor.

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

Tenga en cuenta que Pizza es inmutable y que los valores de los parámetros están todos en una sola ubicación. Debido a que los métodos setter de Builder devuelven el objeto Builder, pueden encadenarse.

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

Esto resulta en un código que es fácil de escribir y muy fácil de leer y entender. En este ejemplo, el método de compilación podría modificarse para verificar los parámetros después de que se hayan copiado del constructor al objeto Pizza y lanzar una excepción IllegalStateException si se ha proporcionado un valor de parámetro no válido. Este patrón es flexible y es fácil agregarle más parámetros en el futuro. Realmente solo es útil si vas a tener más de 4 o 5 parámetros para un constructor. Dicho esto, podría valer la pena en primer lugar si sospecha que puede estar agregando más parámetros en el futuro.

He pedido prestado fuertemente en este tema del libro Java Eficaz, 2da Edición por Joshua Bloch. Para aprender más sobre este patrón y otras prácticas Java efectivas lo recomiendo encarecidamente.

 930
Author: Aaron,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-06-11 12:39:16

Considere un restaurante. La creación de "la comida de hoy" es un patrón de fábrica, porque le dices a la cocina "consígueme la comida de hoy" y la cocina (fábrica) decide qué objeto generar, basado en criterios ocultos.

El constructor aparece si pides una pizza personalizada. En este caso, el camarero le dice al chef (constructor) " Necesito una pizza; ¡agrégale queso, cebolla y tocino!"Por lo tanto, el constructor expone los atributos que el objeto generado debería tener, pero oculta cómo configurarlos.

 299
Author: Tetha,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-11-01 18:20:00

La clase.NET StringBuilder es un gran ejemplo de patrón de constructor. Se utiliza principalmente para crear una cadena en una serie de pasos. El resultado final que se obtiene al hacer toString () es siempre una cadena, pero la creación de esa cadena varía de acuerdo con las funciones de la clase StringBuilder que se usaron. En resumen, la idea básica es construir objetos complejos y ocultar los detalles de implementación de cómo se está construyendo.

 16
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-03-20 01:46:00

Para un problema multihilo, necesitábamos construir un objeto complejo para cada subproceso. El objeto representaba los datos que se estaban procesando y podía cambiar dependiendo de la entrada del usuario.

Podríamos usar una fábrica en su lugar? Sí

¿Por qué no lo hicimos? Constructor tiene más sentido, supongo.

Las fábricas se utilizan para crear diferentes tipos de objetos que son del mismo tipo básico (implementar la misma interfaz o clase base).

Los constructores construyen el mismo tipo de objeto una y otra vez, pero la construcción es dinámica por lo que se puede cambiar en tiempo de ejecución.

 9
Author: Cameron MacFarland,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-11-30 05:51:17

Lo usas cuando tienes muchas opciones con las que lidiar. Piensa en cosas como jmock:

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

Se siente mucho más natural y lo es...posible.

También hay construcción xml, construcción de cadenas y muchas otras cosas. Imagina si java.util.Map se hubiera puesto como constructor. Puedes hacer cosas como esta:

Map<String, Integer> m = new HashMap<String, Integer>()
    .put("a", 1)
    .put("b", 2)
    .put("c", 3);
 6
Author: Dustin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-11-30 05:50:14

Mientras pasaba por Microsoft MVC framework, tuve un pensamiento sobre el patrón de constructor. Me encontré con el patrón en la clase ControllerBuilder. Esta clase es devolver la clase de fábrica del controlador, que luego se usa para construir el controlador de concreto.

La ventaja que veo en el uso de builder pattern es que, puede crear una fábrica propia y conectarla al marco.

@Tetha, puede haber un restaurante (Marco) dirigido por un chico italiano, que sirve Pizza. Con el fin de preparar pizza Italian guy (Constructor de objetos) utiliza Owen (Fábrica) con una base de pizza (clase base).

Ahora el indio se hace cargo del restaurante del italiano. Restaurante indio (Marco) servidores dosa en lugar de pizza. Para preparar dosa Indian guy (object builder) utiliza una Sartén (Fábrica) con una Maida (clase base)

Si nos fijamos en el escenario, la comida es diferente,la forma en que se prepara la comida es diferente, pero en el mismo restaurante (bajo el mismo marco). Restaurante debe ser construido en tal una manera que puede apoyar la cocina china, mexicana o cualquier cocina. Object builder inside framework facilita el tipo de complemento de cocina que desea. por ejemplo

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;
   }
}
 6
Author: Nitin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-09-21 11:57:42

Otra ventaja del constructor es que si tienes una Fábrica, todavía hay algún acoplamiento en tu código, porque para que la Fábrica funcione, tiene que conocer todos los objetos que pueda crear. Si agrega otro objeto que podría ser creado, tendrá que modificar la clase factory para incluirlo. Esto sucede también en la Fábrica Abstracta.

Con el constructor, por otro lado, solo tienes que crear un nuevo constructor de concreto para esta nueva clase. La clase de director permanecerá igual, porque recibe el constructor en el constructor.

También, hay muchos sabores de builder. El mercenario Kamikaze da otro.

 5
Author: Lino Rosa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-12-24 00:20:42
/// <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
Author: Raman Zhylich,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-01-16 12:55:36

Basándose en las respuestas anteriores (juego de palabras), un excelente ejemplo del mundo real es Groovy construido en soporte para Builders.

Ver Constructores en la Documentación Groovy

 5
Author: Ken Gentle,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-29 16:41:59

Siempre me disgustó el patrón de Constructor como algo difícil de manejar, molesto y muy a menudo abusado por programadores menos experimentados. Es un patrón que solo tiene sentido si necesita ensamblar el objeto a partir de algunos datos que requieren un paso posterior a la inicialización (es decir, una vez que se recopilen todos los datos, haga algo con él). En cambio, en el 99% de las veces, los constructores se utilizan simplemente para inicializar a los miembros de la clase.

En tales casos, es mucho mejor declarar simplemente withXyz(...) fijadores de tipos dentro de la clase y hacer que devuelvan una referencia a sí misma.

Considere esto:

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

Ahora tenemos una clase única ordenada que administra su propia inicialización y hace más o menos el mismo trabajo que el constructor, excepto que es mucho más elegante.

 3
Author: Pavel Lechev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-10-07 13:16:29

Usé builder en la biblioteca de mensajería casera. El núcleo de la biblioteca estaba recibiendo datos del cable, recopilándolos con la instancia de Builder, luego, una vez que Builder decidió que tenía todo lo que necesitaba para crear una instancia de mensaje, Builder.getMessage () estaba construyendo una instancia de mensaje usando los datos recopilados del cable.

 2
Author: wasker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-11-30 18:05:42

Cuando quise usar el estándar XMLGregorianCalendar para mi XML para ordenar objetos de DateTime en Java, escuché muchos comentarios sobre lo pesado y engorroso que era usarlo. Estaba tratando de controlar los campos XML en las estructuras xs: datetime para administrar la zona horaria, milisegundos,etc.

Así que diseñé una utilidad para construir un calendario XMLGregorian desde un GregorianCalendar o java.útil.Fecha.

Debido a donde trabajo no se me permite compartirlo en línea sin, pero aquí hay un ejemplo de cómo un cliente lo usa. Abstrae los detalles y filtra parte de la implementación de XMLGregorianCalendar que se usa menos para xs: datetime.

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

Dado que este patrón es más un filtro, ya que establece campos en el xmlCalendar como indefinidos para que se excluyan, todavía lo "construye". He agregado fácilmente otras opciones al constructor para crear una estructura xs:date y xs:time y también para manipular compensaciones de zona horaria cuando sea necesario.

Si alguna vez has visto código que crea y usa XMLGregorianCalendar, verá cómo esto lo hizo mucho más fácil de manipular.

 2
Author: John Brown,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-10-04 11:01:57

Echa un vistazo a InnerBuilder, un plugin de IntelliJ IDEA que añade una acción 'Builder' al menú Generate (Alt + Insert) que genera una clase builder interna como se describe en Effective Java

Https://github.com/analytically/innerbuilder

 1
Author: analytically,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-05-20 08:43:03

Un gran ejemplo del mundo real es usar cuando se prueban las clases unitarias. Se utilizan constructores sut (Sistema Bajo prueba).

Ejemplo:

Clase:

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;

}

Prueba:

    [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
Author: Rafael Miceli,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-03-16 17:34:49