Quali sono alcuni comuni , esempi reali di utilizzo del builder? Cosa ti compra? Perché non usare semplicemente un modello di fabbrica?
Quali sono alcuni comuni , esempi reali di utilizzo del builder? Cosa ti compra? Perché non usare semplicemente un modello di fabbrica?
Risposte:
La differenza chiave tra un costruttore e un IMHO di fabbrica è che un costruttore è utile quando è necessario fare molte cose per costruire un oggetto. Ad esempio, immagina un DOM. Devi creare molti nodi e attributi per ottenere il tuo oggetto finale. Una factory viene utilizzata quando la factory può facilmente creare l'intero oggetto all'interno di una chiamata di metodo.
Un esempio di utilizzo di un builder è la costruzione di un documento XML, ho usato questo modello per costruire frammenti HTML, ad esempio potrei avere un Builder per costruire un tipo specifico di tabella e potrebbe avere i seguenti metodi (i parametri non sono mostrati) :
BuildOrderHeaderRow()
BuildLineItemSubHeaderRow()
BuildOrderRow()
BuildLineItemSubRow()
Questo costruttore avrebbe quindi sputato il codice HTML per me. È molto più facile da leggere che non seguire un metodo procedurale di grandi dimensioni.
Dai un'occhiata al Builder Pattern su Wikipedia .
Di seguito sono riportati alcuni motivi che sostengono l'uso del pattern e del codice di esempio in Java, ma è un'implementazione del Builder Pattern coperto da Gang of Four in Design Patterns . Le ragioni per cui lo useresti in Java sono applicabili anche ad altri linguaggi di programmazione.
Come afferma Joshua Bloch in Effective Java, 2nd Edition :
Il modello del costruttore è una buona scelta quando si progettano classi i cui costruttori o fabbriche statiche avrebbero più di una manciata di parametri.
Ad un certo punto tutti abbiamo incontrato una classe con un elenco di costruttori in cui ogni aggiunta aggiunge un nuovo parametro di opzione:
Pizza(int size) { ... }
Pizza(int size, boolean cheese) { ... }
Pizza(int size, boolean cheese, boolean pepperoni) { ... }
Pizza(int size, boolean cheese, boolean pepperoni, boolean bacon) { ... }
Questo è chiamato il modello del costruttore telescopico. Il problema con questo modello è che una volta che i costruttori sono lunghi 4 o 5 parametri, diventa difficile ricordare l' ordine richiesto dei parametri e quale costruttore specifico si potrebbe desiderare in una data situazione.
Un'alternativa è necessario il modello telescopica Constructor è il modello JavaBean cui si chiama un costruttore con i parametri obbligatori e quindi chiamare qualsiasi setter opzionali dopo:
Pizza pizza = new Pizza(12);
pizza.setCheese(true);
pizza.setPepperoni(true);
pizza.setBacon(true);
Il problema qui è che, poiché l'oggetto viene creato su più chiamate, potrebbe trovarsi in uno stato incoerente durante la sua costruzione. Ciò richiede anche uno sforzo supplementare per garantire la sicurezza della filettatura.
La migliore alternativa è utilizzare il Builder Pattern.
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;
}
}
Si noti che la pizza è immutabile e che i valori dei parametri sono tutti in un'unica posizione . Poiché i metodi setter del Builder restituiscono l'oggetto Builder, possono essere concatenati .
Pizza pizza = new Pizza.Builder(12)
.cheese(true)
.pepperoni(true)
.bacon(true)
.build();
Ciò si traduce in un codice facile da scrivere e molto facile da leggere e comprendere. In questo esempio, il metodo di generazione potrebbe essere modificato per controllare i parametri dopo che sono stati copiati dal builder nell'oggetto Pizza e generare un'eccezione IllegalStateException se è stato fornito un valore di parametro non valido. Questo modello è flessibile ed è facile aggiungere ulteriori parametri in futuro. È davvero utile solo se hai più di 4 o 5 parametri per un costruttore. Detto questo, potrebbe essere utile in primo luogo se sospetti di poter aggiungere altri parametri in futuro.
Ho preso molto in prestito questo argomento dal libro Effective Java, 2nd Edition di Joshua Bloch. Per saperne di più su questo modello e altre pratiche Java efficaci, lo consiglio vivamente.
new Pizza.Builder(12).cheese().pepperoni().bacon().build();
Pizza.Builder(12).cheese().pepperoni().bacon().build();
dovresti ricompilare il tuo codice o avere una logica non necessaria se hai solo bisogno di peperoni pizze. Per lo meno, dovresti anche fornire versioni parametrizzate come suggerito originariamente da @Kamikaze Mercenary. Pizza.Builder(12).cheese(true).pepperoni(false).bacon(false).build();
. Inoltre, non effettuiamo mai test unitari, vero?
Prendi in considerazione un ristorante. La creazione del "pasto di oggi" è un modello di fabbrica, perché dici alla cucina "prendimi il pasto di oggi" e la cucina (fabbrica) decide quale oggetto generare, in base a criteri nascosti.
Il builder appare se ordini una pizza personalizzata. In questo caso, il cameriere dice allo chef (costruttore) "Ho bisogno di una pizza; aggiungi formaggio, cipolle e pancetta!" Pertanto, il builder espone gli attributi che l'oggetto generato dovrebbe avere, ma nasconde come impostarli.
La classe .NET StringBuilder è un ottimo esempio di modello di generatore. Viene utilizzato principalmente per creare una stringa in una serie di passaggi. Il risultato finale ottenuto facendo ToString () è sempre una stringa ma la creazione di quella stringa varia in base alle funzioni utilizzate nella classe StringBuilder. Per riassumere, l'idea di base è costruire oggetti complessi e nascondere i dettagli di implementazione di come viene costruito.
b.append(...).append(...)
prima di chiamare finalmente toString()
. Citazione: infoq.com/articles/internal-dsls-java
Per un problema multi-thread, avevamo bisogno di costruire un oggetto complesso per ogni thread. L'oggetto rappresentava i dati in elaborazione e potrebbe cambiare in base all'input dell'utente.
Potremmo usare una fabbrica invece? sì
Perché non l'abbiamo fatto? Il costruttore ha più senso immagino.
Le fabbriche vengono utilizzate per creare diversi tipi di oggetti dello stesso tipo di base (implementare la stessa interfaccia o classe di base).
I costruttori costruiscono ripetutamente lo stesso tipo di oggetto, ma la costruzione è dinamica, quindi può essere modificata in fase di esecuzione.
Lo usi quando hai molte opzioni da affrontare. Pensa a cose come jmock:
m.expects(once())
.method("testMethod")
.with(eq(1), eq(2))
.returns("someResponse");
Sembra molto più naturale ed è ... possibile.
C'è anche xml building, string building e molte altre cose. Immagina se java.util.Map
fosse stato messo come costruttore. Potresti fare cose come questa:
Map<String, Integer> m = new HashMap<String, Integer>()
.put("a", 1)
.put("b", 2)
.put("c", 3);
Mentre attraversavo il framework Microsoft MVC, ho pensato al modello di generatore. Mi sono imbattuto nel modello nella classe ControllerBuilder. Questa classe restituisce la classe di fabbrica del controller, che viene quindi utilizzata per costruire un controller concreto.
Il vantaggio che vedo nell'uso del pattern builder è che puoi creare una tua fabbrica e collegarla al framework.
@Tetha, può esserci un ristorante (Framework) gestito da un ragazzo italiano che serve pizza. Per preparare la pizza un ragazzo italiano (Object Builder) usa Owen (Factory) con una base per pizza (classe base).
Ora un ragazzo indiano prende il posto del ristorante da un ragazzo italiano. Server indiano (Framework) server dosa invece di pizza. Per preparare il ragazzo indiano dosa (costruttore di oggetti) usa la padella (fabbrica) con un Maida (classe base)
Se si guarda allo scenario, il cibo è diverso, il modo in cui viene preparato il cibo è diverso, ma nello stesso ristorante (sotto lo stesso quadro). Il ristorante dovrebbe essere costruito in modo tale da supportare cinese, messicano o qualsiasi cucina. Object builder all'interno di framework facilita il plug-in del tipo di cucina che desideri. per esempio
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;
}
}
Non ho sempre apprezzato il modello Builder come qualcosa di ingombrante, invadente e molto spesso abusato da programmatori meno esperti. È un modello che ha senso solo se è necessario assemblare l'oggetto da alcuni dati che richiedono una fase di post-inizializzazione (cioè una volta raccolti tutti i dati - fare qualcosa con esso). Al contrario, nel 99% dei casi i costruttori vengono semplicemente utilizzati per inizializzare i membri della classe.
In tali casi è meglio dichiarare semplicemente i withXyz(...)
setter di tipi all'interno della classe e farli restituire un riferimento a se stesso.
Considera questo:
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");
Ora abbiamo una singola classe ordinata che gestisce la propria inizializzazione e fa praticamente lo stesso lavoro del costruttore, tranne che è molto più elegante.
Un altro vantaggio del builder è che se hai una Factory, c'è ancora qualche accoppiamento nel tuo codice, perché affinché Factory funzioni, deve conoscere tutti gli oggetti che può eventualmente creare . Se aggiungi un altro oggetto che potrebbe essere creato, dovrai modificare la classe di fabbrica per includerlo. Questo succede anche nella Fabbrica astratta.
Con il costruttore, d'altra parte, devi solo creare un nuovo costruttore concreto per questa nuova classe. La classe director rimarrà la stessa, perché riceve il builder nel costruttore.
Inoltre, ci sono molti gusti di builder. Kamikaze Mercenary`s ne dà un altro.
/// <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);
}
}
Sulla base delle risposte precedenti (gioco di parole), un eccellente esempio del mondo reale è il supporto integrato di GroovyBuilders
.
MarkupBuilder
StreamingMarkupBuilder
SwingXBuilder
Vedi Builders nella documentazione di Groovy
Ho usato builder nella libreria di messaggistica di casa. Il core della libreria stava ricevendo i dati dal filo, raccogliendoli con l'istanza di Builder, quindi, una volta che Builder aveva deciso di avere tutto il necessario per creare un'istanza di messaggio, Builder.GetMessage () stava costruendo un'istanza di messaggio utilizzando i dati raccolti dal filo.
Dai un'occhiata a InnerBuilder, un plug-in IDEA IntelliJ che aggiunge un'azione 'Builder' al menu Genera (Alt + Inserisci) che genera una classe builder interna come descritto in Effective Java
Quando volevo usare lo standard XMLGregorianCalendar per il mio XML per obiettare al marshalling di DateTime in Java, ho sentito molti commenti su quanto fosse pesante e ingombrante usarlo. Stavo cercando di controllare i campi XML in xs: strutture datetime per gestire fuso orario, millisecondi, ecc.
Quindi ho progettato un'utilità per creare un calendario XMLGregorian da un GregorianCalendar o java.util.Date.
A causa del luogo in cui lavoro, non sono autorizzato a condividerlo online senza restrizioni legali, ma ecco un esempio di come un client lo utilizza. Estrae i dettagli e filtra parte dell'implementazione di XMLGregorianCalendar che sono meno utilizzati per xs: datetime.
XMLGregorianCalendarBuilder builder = XMLGregorianCalendarBuilder.newInstance(jdkDate);
XMLGregorianCalendar xmlCalendar = builder.excludeMillis().excludeOffset().build();
Concesso questo modello è più di un filtro poiché imposta i campi in xmlCalendar come indefinito in modo che siano esclusi, lo "costruisce" ancora. Ho facilmente aggiunto altre opzioni al builder per creare una struttura xs: date e xs: time e anche per manipolare gli offset del fuso orario quando necessario.
Se hai mai visto il codice che crea e utilizza XMLGregorianCalendar, vedresti come questo ha reso molto più facile la manipolazione.
Un ottimo esempio del mondo reale è quello di utilizzare quando testate le unità delle vostre classi. Si usano costruttori sut (System Under Test).
Esempio:
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;
}
Test:
[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
classe?