Pregunta

¿Cuáles son algunos comunes , ejemplos del mundo real del uso del Patrón de construcción? ¿Qué te compra? ¿Por qué no solo usar un patrón de fábrica?

¿Fue útil?

Solución

La diferencia clave entre un constructor y una IMHO de fábrica, es que un constructor es útil cuando necesita 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 de método.

Un ejemplo de uso de un generador es la creación de un documento XML. He utilizado este modelo al crear fragmentos HTML, por ejemplo, podría tener un generador para crear un tipo específico de tabla y podría tener los siguientes métodos (los parámetros no se muestran) :

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

Este constructor escupe el HTML por mí. Esto es mucho más fácil de leer que recorrer un método de procedimiento grande.

Consulte Patrón de generador en Wikipedia .

Otros consejos

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

Como Joshua Bloch dice 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ían 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 Patrón de constructor telescópico. El problema con este patrón es que una vez que los constructores tienen 4 o 5 parámetros, se hace difícil de recordar el requerido orden de los parámetros , así como qué constructor particular puede desear en una situación dada.

Una alternativa que tiene al Patrón de Constructor Telescópico es el Patrón JavaBean donde llama a un constructor con los parámetros obligatorios y luego llama a cualquier setter 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 a través de varias llamadas, puede estar en un estado inconsistente a la mitad de su construcción. Esto también requiere un gran esfuerzo adicional para garantizar la seguridad del hilo.

La mejor alternativa es usar el Patrón de construcción.

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 única ubicación . Debido a que los métodos de establecimiento del generador devuelven el objeto generador, pueden encadenarse .

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

Esto da como resultado un código que es fácil de escribir y muy fácil de leer y comprender. En este ejemplo, el método de compilación podría modificarse para verificar los parámetros después de que hayan se ha copiado del generador al objeto Pizza y arroja una 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 va 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 agregar más parámetros en el futuro.

He tomado prestado mucho sobre este tema del libro Effective Java, 2nd Edition de Joshua Bloch. Para obtener más información sobre este patrón y otras prácticas eficaces de Java, lo recomiendo encarecidamente.

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

El generador aparece si solicita 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 debe tener el objeto generado, pero oculta cómo configurarlos.

La clase

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

Para un problema de subprocesos múltiples, necesitábamos construir un objeto complejo para cada subproceso. El objeto representaba los datos que se procesaban y podía cambiar según 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.

Mientras revisaba el marco de Microsoft MVC, pensé en el patrón del generador. Encontré el patrón en la clase ControllerBuilder. Esta clase es para devolver la clase de fábrica del controlador, que luego se utiliza para construir un controlador concreto.

La ventaja que veo al usar el patrón de construcción es que puede crear una fábrica propia y conectarla al marco.

@Tetha, puede haber un restaurante (Framework) dirigido por un chico italiano que sirve pizza. Para preparar pizza, el tipo italiano (Object Builder) usa Owen (Factory) con una base de pizza (clase base).

Ahora el chico indio se hace cargo del restaurante del italiano. El restaurante indio (Framework) sirve dosa en lugar de pizza. Para preparar dosa, el tipo indio (constructor de objetos) usa Sartén (Fábrica) con una Maida (clase base)

Si observa 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). El restaurante debe construirse de tal manera que pueda soportar cocina china, mexicana o de cualquier tipo. El creador de objetos dentro del marco facilita agregar el tipo de cocina que desee. 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;
   }
}

Lo usa cuando tiene 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 es ... posible.

También hay construcción xml, construcción de cadenas y muchas otras cosas. Imagínese si java.util.Map se hubiera puesto como constructor. Podrías hacer cosas como esta:

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

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

Ver Constructores en Documentación maravillosa

Otra ventaja del constructor es que si tiene una Fábrica, todavía hay algo de acoplamiento en su código, porque para que la Fábrica funcione, tiene que conocer todos los objetos que puede crear . Si agrega otro objeto que podría crearse, deberá modificar la clase de fábrica para incluirlo. Esto también sucede en Abstract Factory.

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

Además, hay muchos sabores de constructor. Kamikaze Mercenary`s da otro.

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

Siempre me disgustó el patrón Builder 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 recopilan todos los datos, haga algo con ellos). En cambio, en el 99% del tiempo, los constructores simplemente se usan para inicializar a los miembros de la clase.

En tales casos, es mucho mejor simplemente declarar los establecedores de tipo withXyz (...) dentro de la clase y hacer que devuelvan una referencia a sí mismo.

Considera 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 gestiona su propia inicialización y hace prácticamente el mismo trabajo que el constructor, excepto que es mucho más elegante.

Utilicé el generador en la biblioteca de mensajería local. 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 necesario para crear una instancia de Mensaje, Builder.GetMessage () estaba construyendo una instancia de mensaje utilizando los datos recopilados del alambre.

Cuando quería usar el XMLGregorianCalendar estándar para mi XML para objetar la clasificación 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 a partir de un calendario Gregorian o java.util.Date.

Debido a dónde trabajo, no puedo compartirlo en línea sin contenido legal, pero aquí hay un ejemplo de cómo lo utiliza un cliente. Resume 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();

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

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

Echa un vistazo a InnerBuilder, un complemento IntelliJ IDEA que agrega una acción 'Generador' al menú Generar (Alt + Insertar) que genera una clase de generador interno como se describe en Effective Java

https://github.com/analytically/innerbuilder

Un gran ejemplo del mundo real es usar cuando la unidad prueba sus clases. Utiliza 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();
    }
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top