当前位置:  开发笔记 > 编程语言 > 正文

你什么时候使用Builder模式?

如何解决《你什么时候使用Builder模式?》经验,为你挑选了11个好方法。

什么是一些常见的,现实世界的例子使用Builder模式的?它给你带来了什么?为什么不使用工厂模式?



1> Aaron..:

下面是一些争论在Java中使用模式和示例代码的原因,但它是由设计模式中的四人组所涵盖的Builder模式的实现.您在Java中使用它的原因也适用于其他编程语言.

正如Joshua Bloch在Effective Java第2版中所述:

在设计构造函数或静态工厂具有多个参数的类时,构建器模式是一个不错的选择.

我们在某个时刻遇到了一个带有构造函数列表的类,其中每个添加都添加了一个新的选项参数:

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

这称为Telescoping Constructor Pattern.这种模式的问题是,一旦构造函数长度为4或5个参数,就很难记住参数的所需顺序以及在给定情况下您可能需要的特定构造函数.

Telescoping构造函数模式的另一个选择JavaBean模式,您可以使用必需参数调用构造函数,然后在以下情况下调用任何可选的setter:

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

这里的问题是因为对象是在几次调用中创建的,所以它的构造中途可能处于不一致状态.这还需要大量额外的努力来确保线程安全.

更好的选择是使用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;
  }
}

请注意,Pizza是不可变的,并且参数值都在一个位置.因为Builder的setter方法返回Builder对象,所以它们可以被链接.

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

这导致代码易于编写且易于阅读和理解.在此示例中,可以修改构建方法,以便在将参数从构建器复制到Pizza对象后检查参数,并在提供了无效参数值时抛出IllegalStateException.这种模式非常灵活,将来很容易为它添加更多参数.只有当你要为构造函数提供超过4或5个参数时,它才真正有用.也就是说,如果您怀疑将来可能会添加更多参数,那么首先可能是值得的.

我在Joshua Bloch的Effective Java,2nd Edition一书中大量借用了这个主题.要了解有关此模式和其他有效Java实践的更多信息,我强烈推荐它.


对于这个特定的例子,删除布尔参数并且能够说"new Pizza.Builder(12).cheese().pepperoni()."bacon().build();
这看起来更像是[Fluent Interface](http://en.wikipedia.org/wiki/Fluent_interface),而不是[builder pattern](http://en.wikipedia.org/wiki/Builder_pattern).
与原来的GOF建设者不同吧?因为没有导演班.似乎对我来说几乎就像另一种模式,但我同意它非常有用.
@JasonC对,有什么用的是不变的披萨呢?
@Fabian Steeg,我认为人们对更好看的布尔设置者反​​应过度,请记住这些设置器不允许运行时更改:`Pizza.Builder(12).cheese().pepperoni().bacon( ).build();`,如果你只需要一些意大利辣香肠比萨,你需要重新编译你的代码或者有不必要的逻辑.至少你应该提供像@Kamikaze Mercenary最初建议的参数化版本.`Pizza.Builder(12).cheese(真).pepperoni(假).bacon(假).build();`.然后,我们再也没有进行单元测试,是吗?
老实说,与javabean模式相比,我发现构建器模式更有效,但收益甚微
@SACO,我可以告诉你我使用该模式的原因是构造没有公共setter的对象.我可以通过这种方式在我的域模型上保持更好的治理.构建器负责确保我只能创建域对象的有效变体,而该对象上的方法允许使用者更改其状态.这些方法可以提供所需的任何保护,并封装执行所需操作所需的所有逻辑和状态更改.如果留给消费者为每个属性等设置新值,则会发生错误/违规.
当然 - 为了举例,我只使用了布尔值.实际上,您很可能会使用构建器来处理更复杂的参数.
@Lino它实际上是GOF Builder的一种形式.
当对象构造函数参数组合的增加导致构造函数的指数列表时,发生**伸缩构造函数反模式**.
@egallardo:所以你告诉我你不会为你烘焙的每一个披萨重建你的烤箱?;-)是的,在那种情况下,你当然是对的.

2> Tetha..:

考虑一家餐馆.创造"今日餐"是一种工厂模式,因为你告诉厨房"让我今天吃饭",厨房(工厂)根据隐藏的标准决定生成什么对象.

如果您订购自定义披萨,则会显示构建器.在这种情况下,服务员告诉厨师(建筑师)"我需要一个披萨;加入奶酪,洋葱和培根!" 因此,构建器公开生成的对象应具有的属性,但隐藏如何设置它们.



3> JoshBerke..:

构建器和工厂IMHO之间的关键区别在于,当您需要执行大量操作来构建对象时,构建器非常有用.例如,想象一下DOM.您必须创建大量节点和属性才能获得最终对象.当工厂可以在一个方法调用中轻松创建整个对象时,使用工厂.

使用构建器的一个示例是构建XML文档,我在构建HTML片段时使用了此模型,例如,我可能有一个构建特定类型的表的构建器,它可能具有以下方法(参数未显示):

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

然后,此构建器将为我吐出HTML.这比通过一个大的程序方法更容易阅读.

查看维基百科上的Builder Pattern.



4> 小智..:

.NET StringBuilder类是构建器模式的一个很好的例子.它主要用于通过一系列步骤创建字符串.你在ToString()上做的最终结果总是一个字符串,但该字符串的创建因StringBuilder类中使用的函数而异.总而言之,基本思想是构建复杂对象并隐藏其构建方式的实现细节.


它绝对是构建器模式,Java中的StringBuilder类也是如此.注意这两个类的append()方法如何返回StringBuilder本身,以便在最终调用`toString()`之前可以链接`b.append(...).append(...)`.引用:http://www.infoq.com/articles/internal-dsls-java
我不认为这是建设者模式.StringBuilder只是字符数组类(即字符串)的另一种实现,但它考虑了性能和内存管理,因为字符串是不可变的.
@pohl Ya我不认为这是一个真正的构建模式,我会说这更像是一个流畅的界面.

5> Cameron MacF..:

对于多线程问题,我们需要为每个线程构建一个复杂的对象.该对象表示正在处理的数据,并且可能根据用户输入而改变.

我们可以用工厂吗?是

我们为什么不呢?我想,生成器更有意义.

工厂用于创建相同基本类型的不同类型的对象(实现相同的接口或基类).

构建器一遍又一遍地构建相同类型的对象,但构造是动态的,因此可以在运行时更改.



6> Dustin..:

当你有很多选择要处理时,你可以使用它.想想像jmock这样的事情:

m.expects(once())
    .method("testMethod")
    .with(eq(1), eq(2))
    .returns("someResponse");

它感觉更自然,而且......可能.

还有xml构建,字符串构建和许多其他东西.想象一下,如果java.util.Map把它作为一个建设者.你可以做这样的事情:

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


地图示例只是方法链的一个示例.
我忘了阅读"if"地图实现了一个构建器模式,很惊讶看到那里的构造.. :)
:) 对于那个很抱歉.在许多语言中,返回自我而非虚空是很常见的.你可以在java中做到这一点,但它并不常见.

7> 小智..:

在浏览Microsoft MVC框架时,我想到了构建器模式.我在ControllerBuilder类中遇到了这个模式.这个类是返回控制器工厂类,然后用于构建具体的控制器.

我在使用构建器模式时看到的优点是,您可以创建自己的工厂并将其插入框架中.

@Tetha,可以有一家由意大利人经营的餐厅(Framework),供应比萨饼.为了准备比萨饼意大利人(Object Builder)使用Owen(工厂)和披萨基地(基础类).

现在印度人从意大利人那里接过餐馆.印度餐厅(框架)服务器dosa而不是披萨.为了准备dosa印度人(对象建设者)使用煎锅(工厂)与Maida(基类)

如果你看情景,食物是不同的,食物的准备方式是不同的,但在同一个餐厅(在相同的框架下).餐厅应该以这样的方式建造,它可以支持中国,墨西哥或任何美食.框架内的对象构建器有助于插入您想要的烹饪类型.例如

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



8> Pavel Lechev..:

我总是不喜欢Builder模式,因为它笨拙,笨拙并且经常被经验不足的程序员滥用。它是一种模式,仅在您需要从需要后初始化步骤的某些数据中组装对象时才有意义(即,一旦收集了所有数据,请对其进行处理)。取而代之的是,在99%的时间内,仅使用构建器来初始化类成员。

在这种情况下,最好只withXyz(...)在类中声明类型设置器,并使它们返回对自身的引用。

考虑一下:

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

现在,我们有了一个简洁的单一类,该类可以管理自己的初始化,并且与生成器几乎一样的工作,除了它的优雅得多。


嗯?您是否在回答我的某处读到了说什么是Builder的?这是对“何时使用构建器模式?”顶部问题的一种替代观点,该问题基于对模式无数次滥用的经验,在这种情况下,简单得多的方法可以更好地完成工作。如果您知道何时以及如何使用它们,那么所有模式都是有用的-首先就是记录这些模式!如果模式被过度使用或更糟(被滥用),则在您的代码上下文中它成为反模式。唉...

9> Ken Gentle..:

在之前的答案(双关语)的基础上,一个优秀的现实世界的例子是Groovy的内置支持Builders.

使用Groovy创建XML MarkupBuilder

使用Groovy创建XML StreamingMarkupBuilder

Swing Builder

SwingXBuilder

见建设者在Groovy的文档



10> Lino Rosa..:

构建器的另一个优点是,如果你有一个Factory,你的代码中仍然会有一些耦合,因为要使Factory工作,它必须知道它可能创建的所有对象.如果添加另一个可以创建的对象,则必须修改工厂类以包含他.这也发生在抽象工厂中.

另一方面,使用构建器,您只需为此新类创建新的具体构建器.导向器类将保持不变,因为它在构造函数中接收构建器.

此外,还有许多口味的建设者.Kamikaze Mercenary的另一个.



11> Raman Zhylic..:
/// 
/// Builder
/// 
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();
}

/// 
/// ConcreteBuilder #1
/// 
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;
    }
}

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

/// 
/// Director
/// 
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
{
    /// 
    /// Inside both requests the same SearchRequest.Construct(string) method is used.
    /// But finally different HttpWebRequest objects are built.
    /// 
    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);
    }
}

推荐阅读
重庆制造漫画社
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有