pattern - patron builder java
¿Cuándo usarías el patrón de construcción? (15)
¿Cuáles son algunos ejemplos comunes y reales del uso del patrón de creación? ¿Qué te compra? ¿Por qué no usar un patrón de fábrica?
A continuación, se incluyen algunos motivos para argumentar el uso del patrón y el código de ejemplo en Java, pero es una implementación del patrón de generador cubierto por la banda de cuatro en los patrones de diseño . Las razones por las que lo utilizarías en Java también son aplicables a otros lenguajes de programación.
Como afirma Joshua Bloch en Effective Java, 2ª 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.
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 constructor telescópico. El problema con este patrón es que una vez que los constructores tienen 4 o 5 parámetros, es difícil recordar el orden requerido de los parámetros , así como el constructor en particular que pueda desear en una situación determinada.
Una alternativa que tiene para el patrón de constructor de telescopios es el patrón de JavaBean donde llama a un constructor con los parámetros obligatorios y luego llama a los configuradores opcionales 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 mitad de su construcción. Esto también requiere mucho esfuerzo adicional para garantizar la seguridad de la rosca.
La mejor alternativa es usar el patrón 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;
}
}
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, se pueden encadenar .
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 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 generador 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 va a tener más de 4 o 5 parámetros para un constructor. Dicho esto, puede que valga la pena, en primer lugar, si sospecha que puede agregar más parámetros en el futuro.
Tomé muchos préstamos 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 efectivas de Java, lo recomiendo ampliamente.
Considere la posibilidad de 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, según los criterios ocultos.
El constructor aparece si pides una pizza personalizada. En este caso, el camarero le dice al chef (constructor) "Necesito una pizza; ¡añada queso, cebollas y tocino!" Por lo tanto, el constructor expone los atributos que debe tener el objeto generado, pero oculta cómo establecerlos.
Cuando quise usar el XMLGregorianCalendar estándar para mi XML para objetar el cálculo de fechas de DateTime en Java, escuché muchos comentarios sobre el peso y la incomodidad de 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.
Por el lugar donde trabajo no puedo compartirlo en línea sin legal, pero aquí hay un ejemplo de cómo lo usa un cliente. Abstrae los detalles y filtra algunas de las implementaciones de XMLGregorianCalendar que se usan 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 xmlCalendar como indefinidos, por lo que se excluyen, 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 las compensaciones de la zona horaria cuando sea necesario.
Si alguna vez has visto un 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 constructor interno como se describe en Effective Java
La clase .NET StringBuilder es un gran ejemplo de patrón de construcción. Se utiliza principalmente para crear una cadena en una serie de pasos. El resultado final que obtienes al hacer ToString () es siempre una cadena, pero la creación de esa cadena varía según las funciones de 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.
La diferencia clave entre un constructor y la IMHO de fábrica, es que un constructor es útil cuando necesitas hacer muchas cosas para construir un objeto. Por ejemplo imagina un DOM. Tienes que crear muchos nodos y atributos para obtener tu objeto final. Se usa una fábrica cuando la fábrica puede crear fácilmente el objeto completo dentro de una llamada de método.
Un ejemplo del uso de un constructor 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 luego escupiría el HTML para mí. Esto es mucho más fácil de leer que caminar a través de un gran método de procedimiento.
Echa un vistazo a Builder Pattern en Wikipedia .
Lo usas cuando tienes muchas opciones para tratar. 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 cuerdas 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 trabajo de Microsoft MVC, pensé en un patrón de construcción. Me encontré con el patrón en la clase ControllerBuilder. Esta clase debe devolver la clase de fábrica del controlador, que luego se usa para construir el controlador concreto.
La ventaja que veo al usar el patrón de construcción es que, puedes crear tu propia fábrica y enchufarla al marco.
@Tetha, puede haber un restaurante (Framework) administrado por un chico italiano, que sirve Pizza. Para preparar la pizza, el chico italiano (Object Builder) usa Owen (Factory) con una base de pizza (clase base).
Ahora chico indio toma el restaurante de chico italiano. Restaurante indio (Framework) servidores dosa en lugar de pizza. Para preparar dosa Indian guy (constructor de objetos) usa Frying Pan (Factory) con un 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). El restaurante debe construirse de tal manera que pueda admitir cocina china, mexicana o de cualquier tipo. El constructor de objetos dentro del marco facilita la instalación de la 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;
}
}
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, debe conocer todos los objetos que posiblemente pueda crear . Si agrega otro objeto que podría crearse, tendrá que modificar la clase de fábrica para incluirlo. Esto sucede también en la fábrica abstracta.
Con el constructor, por otro lado, solo tienes que crear un nuevo constructor concreto para esta nueva clase. La clase de director permanecerá igual, porque recibe el constructor en el constructor.
Además, hay muchos sabores de constructor. Kamikaze Mercenary`s da otro.
Para un problema de múltiples subprocesos, necesitábamos que se construyera un objeto complejo para cada subproceso. El objeto representó los datos que se procesan y podría cambiar según la entrada del usuario.
¿Podríamos usar una fábrica en su lugar? Sí
¿Por qué no lo hicimos? El 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.
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 él). En cambio, en el 99% del tiempo, los constructores simplemente se utilizan para inicializar a los miembros de la clase.
En tales casos, es mucho mejor declarar con withXyz(...)
tipo withXyz(...)
dentro de la clase y hacer que devuelvan una referencia a sí mismos.
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 administra su propia inicialización y hace prácticamente el mismo trabajo que el constructor, excepto que es mucho más elegante.
Sobre la base de las respuestas anteriores (un juego de palabras), un excelente ejemplo del mundo real es el soporte integrado de Groovy para Builders
.
- Creando XML utilizando el
MarkupBuilder
de Groovy - Creando XML utilizando
StreamingMarkupBuilder
de Groovy - Constructor de swing
-
SwingXBuilder
Ver Builders en la Documentación Groovy
Un gran ejemplo del mundo real es usar cuando una unidad prueba tus clases. Usas constructores sut (System Under Test).
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();
}
}
Utilicé constructor en la biblioteca de mensajes de cosecha propia. El núcleo de la biblioteca estaba recibiendo datos del cable, recolectándolos con la instancia de Builder, luego, una vez que Builder decidió que tenía todo lo necesario para crear una instancia de Message, Builder.GetMessage () estaba construyendo una instancia de mensaje utilizando los datos recopilados de cable.
/// <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);
}
}