你什么时候使用Builder模式?

使用Builder模式的一些常见的 真实世界的例子是什么? 它是什么给你买的? 为什么不使用工厂模式?

build设者和工厂恕我直言,主要区别是build设者是有用的,当你需要做很多事情来build立一个对象。 例如想象一个DOM。 您必须创build大量的节点和属性才能获得最终的对象。 当工厂可以在一个方法调用中轻松地创build整个对象时使用工厂。

使用构build器的一个例子是构build一个XML文档,在构buildHTML片段时我使用了这个模型,例如我可能有一个Builder来构build特定types的表,并且可能有以下方法(参数不显示)

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

然后这个build造者会为我吐出HTML。 这是更容易阅读,然后通过一个大的程序方法。

检查维基百科上的生成器模式 。

以下是在Java中使用模式和示例代码的一些理由,但它是“四人帮”在devise模式中所涵盖的Builder模式的实现。 您在Java中使用它的原因也适用于其他编程语言。

正如Joshua Bloch在Effective Java,2nd Edition中所述 :

当devise构造函数或静态工厂只有less数几个参数的类时,构build器模式是一个很好的select。

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

 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构造器模式。 这种模式的问题是,一旦构造函数的参数是4或5个参数,就很难记住所需的参数顺序以及在特定情况下可能需要的特定构造函数。

Telescoping构造函数模式的一个替代方法JavaBean模式 ,您可以使用必需的参数调用构造函数,然后在调用任何可选的setter方法之后:

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

这里的问题在于,因为这个对象是通过多次调用创build的,所以它在构build过程中可能处于不一致的状态。 这也需要很多额外的努力来确保线程安全。

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

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

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

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

我从Joshua Bloch的“ Effective Java,2nd Edition ”一书中大量借鉴了这个主题。 要了解更多关于这种模式和其他有效的Java实践, 我强烈推荐它。

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

如果您订购自定义披萨,build造者就会出现。 在这种情况下,服务员告诉厨师(build设者):“我需要比萨饼,join奶酪,洋葱和培根!” 因此,构build器暴露了生成的对象应具有的属性,但隐藏了如何设置它们。

.NET StringBuilder类是构build器模式的一个很好的例子。 它主要用于在一系列步骤中创build一个string。 你在做ToString()的最后结果总是一个string,但是根据StringBuilder类中的函数的使用,该string的创build是不同的。 总而言之,基本思想是构build复杂的对象,并隐藏它如何构build的实现细节。

对于multithreading问题,我们需要为每个线程构build一个复杂的对象。 该对象表示正在处理的数据,并可能根据用户input而改变。

我们可以用工厂吗? 是

我们为什么不呢? build设者更有意义我猜。

工厂用于创build不同types的基本types相同的对象(实现相同的接口或基类)。

build设者一遍又一遍地构build相同types的对象,但是构造是dynamic的,所以它可以在运行时改变。

当你有很多select来处理时,你可以使用它。 想想像jmock这样的东西:

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

这感觉更自然,是…可能的。

还有xml的build设,build立string和许多其他的东西。 想象一下,如果java.util.Map已经作为一个build造者。 你可以做这样的事情:

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

在经历微软MVC框架的同时,我对构build模式有了一个想法。 我遇到了ControllerBuilder类中的模式。 这个类是返回控制器工厂类,然后用它来构build具体的控制器。

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

@Tetha,可以有一家意大利人经营的餐厅(框架),供应披萨。 为了准备比萨意大利人(对象build设者)使用欧文(工厂)与披萨基地(基础类)。

现在印度人从意大利人手中接过餐厅。 印度餐馆(框架)服务器dosa而不是披萨。 为了准备dosa印度人(对象build设者)使用煎锅(工厂)与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; } } 

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

另一方面,build造者,你只需要为这个新class级build立一个新的混凝土build造者。 导演类将保持不变,因为它在构造函数中接收构build器。

另外,还有很多build筑师的口味。 神风雇佣兵给另一个。

基于以前的答案(双关意图),一个很好的现实世界的例子是Groovy的build造者支持。

  • 使用Groovy的MarkupBuilder创buildXML
  • 使用Groovy的StreamingMarkupBuilder创buildXML
  • 摇摆build设者
  • SwingXBuilder

请参阅Groovy文档中的 构build器

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

我在本土的消息库中使用了builder。 图书馆的核心是从networking接收数据,用Builder实例收集数据,然后,一旦Builder认为它已经得到了创build一个Message实例所需的一切,Builder.GetMessage()就使用从这个收集的数据构build了一个消息实例线。

当我想用我的XML的标准XMLGregorianCalendar来反编译Java中的DateTime时,我听到很多关于使用它的重量和麻烦的评论。 我试图控制xs:datetime结构中的XML字段来pipe理时区,毫秒等。

所以我devise了一个实用程序来从GregorianCalendar或java.util.Date构build一个XMLGregorian日历。

由于我在哪里工作,我不允许在没有合法的情况下在线分享它,但这里有一个客户如何使用它的例子。 它抽象详细信息并过滤一些XMLGregorianCalendar的实现,这些实现不太用于xs:datetime。

 XMLGregorianCalendarBuilder builder = XMLGregorianCalendarBuilder.newInstance(jdkDate); XMLGregorianCalendar xmlCalendar = builder.excludeMillis().excludeOffset().build(); 

当然,这个模式更像是一个filter,因为它将xmlCalendar中的字段设置为undefined,所以它们被排除,它仍然“构build”它。 我很容易地将其他选项添加到生成器来创build一个xs:date和xs:time结构,并且还可以在需要时处理时区偏移量。

如果你见过创build和使用XMLGregorianCalendar的代码,你会看到这是如何更容易操作的。

我总是不喜欢Builder的模式,这种模式很笨重,令人反感,经常被经验不足的程序员滥用。 它的一种模式,只有在需要从一些需要初始化后步骤的数据(也就是一旦收集到所有的数据 – 用它做一些事情)来组装对象时才有意义。 相反,在99%的时间内,build造者只是用来初始化class级成员。

在这种情况下,简单地在类中声明withXyz(...)types设置器并使它们返回对自身的引用会好得多。

考虑这个:

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

现在,我们有一个整洁的单一类,它pipe理自己的初始化,完成与构build器几乎相同的工作,除了它更优雅。

查看InnerBuilder,一个IntelliJ IDEA插件,它向“生成”菜单(Alt + Insert)添加一个“生成器”操作,生成一个内部生成器类,如Effective Java

https://github.com/analytically/innerbuilder

一个伟大的现实世界的例子是当unit testing你的类时使用。 您使用sut(系统在testing)build设者。

例:

类:

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

testing:

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

sutbuild设者:

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