你什麼時候使用Builder模式? [關閉]

本文翻譯自:When would you use the Builder Pattern? [closed]

What are some common , real world examples of using the Builder Pattern? 使用Builder Pattern有哪些常見的 現實世界示例 What does it buy you? 它給你帶來了什麼? Why not just use a Factory Pattern? 爲什麼不使用工廠模式?


#1樓

參考:https://stackoom.com/question/1NSK/你什麼時候使用Builder模式-關閉


#2樓

Below are some reasons arguing for the use of the pattern and example code in Java, but it is an implementation of the Builder Pattern covered by the Gang of Four in Design Patterns . 下面是一些爭論在Java中使用模式和示例代碼的原因,但它是由設計模式中的四人組所涵蓋的Builder模式的實現。 The reasons you would use it in Java are also applicable to other programming languages as well. 您在Java中使用它的原因也適用於其他編程語言。

As Joshua Bloch states in Effective Java, 2nd Edition : 正如Joshua Bloch在Effective Java第2版中所述

The builder pattern is a good choice when designing classes whose constructors or static factories would have more than a handful of parameters. 在設計構造函數或靜態工廠具有多個參數的類時,構建器模式是一個不錯的選擇。

We've all at some point encountered a class with a list of constructors where each addition adds a new option parameter: 我們在某個時刻遇到了一個帶有構造函數列表的類,其中每個添加都添加了一個新的選項參數:

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

This is called the Telescoping Constructor Pattern. 這稱爲Telescoping Constructor Pattern。 The problem with this pattern is that once constructors are 4 or 5 parameters long it becomes difficult to remember the required order of the parameters as well as what particular constructor you might want in a given situation. 這種模式的問題是,一旦構造函數長度爲4或5個參數,就很難記住 參數的所需順序以及在給定情況下您可能需要的特定構造函數。

One alternative you have to the Telescoping Constructor Pattern is the JavaBean Pattern where you call a constructor with the mandatory parameters and then call any optional setters after: Telescoping構造函數模式的另一個選擇JavaBean模式 ,您可以使用必需參數調用構造函數,然後在以下情況下調用任何可選的setter:

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

The problem here is that because the object is created over several calls it may be in an inconsistent state partway through its construction. 這裏的問題是因爲對象是在幾次調用中創建的,所以它的構造中途可能處於不一致狀態。 This also requires a lot of extra effort to ensure thread safety. 這還需要大量額外的努力來確保線程安全。

The better alternative is to use the Builder Pattern. 更好的選擇是使用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;
  }
}

Note that Pizza is immutable and that parameter values are all in a single location . 請注意, Pizza是不可變的,並且參數值都在一個位置 Because the Builder's setter methods return the Builder object they are able to be chained . 因爲Builder的setter方法返回Builder對象,所以它們可以被鏈接

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

This results in code that is easy to write and very easy to read and understand. 這導致代碼易於編寫且易於閱讀和理解。 In this example, the build method could be modified to check parameters after they have been copied from the builder to the Pizza object and throw an IllegalStateException if an invalid parameter value has been supplied. 在此示例中, 可以修改構建方法,以便在將參數從構建器複製到Pizza對象後檢查參數, 如果提供了無效的參數值 ,則拋出IllegalStateException。 This pattern is flexible and it is easy to add more parameters to it in the future. 這種模式非常靈活,將來很容易爲它添加更多參數。 It is really only useful if you are going to have more than 4 or 5 parameters for a constructor. 只有當你要爲構造函數提供超過4或5個參數時,它才真正有用。 That said, it might be worthwhile in the first place if you suspect you may be adding more parameters in the future. 也就是說, 如果您懷疑將來可能會添加更多參數 ,那麼首先可能是值得的。

I have borrowed heavily on this topic from the book Effective Java, 2nd Edition by Joshua Bloch. 我在Joshua Bloch的Effective Java,2nd Edition一書中大量借用了這個主題。 To learn more about this pattern and other effective Java practices I highly recommend it. 要了解有關此模式和其他有效Java實踐的更多信息, 我強烈推薦它。


#3樓

Another advantage of the builder is that if you have a Factory, there is still some coupling in you code, because for the Factory to work, it has to know all the objects it can possibly create . 構建器的另一個優點是,如果你有一個Factory,你的代碼中仍然會有一些耦合,因爲要使Factory工作,它必須知道它可能創建的所有對象 If you add another object that could be created, you will have to modify the factory class to include him. 如果添加另一個可以創建的對象,則必須修改工廠類以包含他。 This happens in the Abstract Factory as well. 這也發生在抽象工廠中。

With the builder, on the other hand, you just have to create a new concrete builder for this new class. 另一方面,使用構建器,您只需爲此新類創建新的具體構建器。 The director class will stay the same, because it receives the builder in the constructor. 導向器類將保持不變,因爲它在構造函數中接收構建器。

Also, there are many flavors of builder. 此外,還有許多口味的建設者。 Kamikaze Mercenary`s gives another one. Kamikaze Mercenary的另一個。


#4樓

/// <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樓

Check out InnerBuilder, an IntelliJ IDEA plugin that adds a 'Builder' action to the Generate menu (Alt+Insert) which generates an inner builder class as described in Effective Java 查看InnerBuilder,這是一個IntelliJ IDEA插件,它將“Builder”動作添加到Generate菜單(Alt + Insert),生成內部構建器類,如Effective Java中所述

https://github.com/analytically/innerbuilder https://github.com/analytically/innerbuilder


#6樓

I always disliked the Builder pattern as something unwieldy, obtrusive and very often abused by less experienced programmers. 我總是不喜歡Builder模式,因爲它不經常,突兀,經常被經驗不足的程序員濫用。 Its a pattern which only makes sense if you need to assemble the object from some data which requires a post-initialisation step (ie once all the data is collected - do something with it). 它是一種模式,只有在你需要從需要後初始化步驟的一些數據中組裝對象時纔會有意義(即一旦收集了所有數據 - 用它做一些事情)。 Instead, in 99% of the time builders are simply used to initialise the class members. 相反,在99%的情況下,構建器僅用於初始化類成員。

In such cases it is far better to simply declare withXyz(...) type setters inside the class and make them return a reference to itself. 在這種情況下,簡單地在類中聲明withXyz(...)類型的setter並使它們返回withXyz(...)自己的引用要好得多。

Consider this: 考慮一下:

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

Now we have a neat single class that manages its own initialization and does pretty much the same job as the builder, except that its far more elegant. 現在我們有一個整潔的單一類來管理它自己的初始化,並且與構建器完成相同的工作,除了它更優雅。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章