¿Cuáles son algunos comunes , ejemplos del mundo real de utilizar el Builder? ¿Qué te compra? ¿Por qué no solo usar un patrón de fábrica?
¿Cuáles son algunos comunes , ejemplos del mundo real de utilizar el Builder? ¿Qué te compra? ¿Por qué no solo usar un patrón de fábrica?
Respuestas:
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. Se utiliza una fábrica 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 (no se muestran los parámetros) :
BuildOrderHeaderRow()
BuildLineItemSubHeaderRow()
BuildOrderRow()
BuildLineItemSubRow()
Este constructor entonces escupió el HTML para mí. Esto es mucho más fácil de leer que recorrer un gran método de procedimiento.
Echa un vistazo a Builder Pattern en Wikipedia .
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 los patrones de diseño . Las razones por las que lo usaría en Java también son aplicables a otros lenguajes de programación.
Como Joshua Bloch dice en Effective Java, 2nd Edition :
El patrón de construcción es una buena opción cuando se diseñan clases cuyos constructores o fábricas estáticas tendrían más de un puñado de parámetros.
En algún momento, todos hemos encontrado 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 de 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 hace difícil recordar el orden requerido de los parámetros , así como también 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 de JavaBean donde llama a un constructor con los parámetros obligatorios y luego llama 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 a través de varias llamadas, puede estar en un estado inconsistente a la mitad de su construcción. Esto también requiere mucho esfuerzo adicional para garantizar la seguridad del hilo.
La mejor alternativa es usar el Patrón de generador.
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 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 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 se hayan copiado del generador al objeto Pizza y arrojar 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.
new Pizza.Builder(12).cheese().pepperoni().bacon().build();
Pizza.Builder(12).cheese().pepperoni().bacon().build();
necesitaría recompilar su código o tener una lógica innecesaria si solo necesita un poco de pepperoni pizzas Como mínimo, también debe proporcionar versiones parametrizadas como @Kamikaze Mercenary originalmente sugerido. Pizza.Builder(12).cheese(true).pepperoni(false).bacon(false).build();
. Por otra parte, nunca hacemos pruebas unitarias, ¿verdad?
Considera un restaurante. La creación de la "comida de hoy" es un patrón de fábrica, porque usted le dice a la cocina "consígueme la comida de hoy" y la cocina (fábrica) decide qué objeto generar, en base a criterios ocultos.
El generador aparece si solicita una pizza personalizada. En este caso, el camarero le dice al chef (constructor) "¡Necesito una pizza; agréguele 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.
b.append(...).append(...)
antes de llamar finalmente toString()
. Cita: infoq.com/articles/internal-dsls-java
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? si
¿Por qué no lo hicimos nosotros? 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.
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. Imagina si java.util.Map
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);
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;
}
}
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 withXyz(...)
tipo 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 casi el mismo trabajo que el constructor, excepto que es mucho más elegante.
Otra ventaja del generador 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);
}
}
Sobre la base de las respuestas anteriores (juego de palabras), un excelente ejemplo del mundo real es el soporte incorporado de GroovyBuilders
.
MarkupBuilder
StreamingMarkupBuilder
SwingXBuilder
Ver constructores en la documentación de Groovy
Utilicé el constructor 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 cable.
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 Java efectivo
Cuando quise 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 desde un GregorianCalendar 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 usa 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();
Por supuesto, 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 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ás cómo esto hizo que sea mucho más fácil de manipular.
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();
}
}
CustomAuthenticationService
clase?