什么是一些常见的,现实世界的例子使用Builder模式的?它给你带来了什么?为什么不使用工厂模式?
下面是一些争论在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实践的更多信息,我强烈推荐它.
考虑一家餐馆.创造"今日餐"是一种工厂模式,因为你告诉厨房"让我今天吃饭",厨房(工厂)根据隐藏的标准决定生成什么对象.
如果您订购自定义披萨,则会显示构建器.在这种情况下,服务员告诉厨师(建筑师)"我需要一个披萨;加入奶酪,洋葱和培根!" 因此,构建器公开生成的对象应具有的属性,但隐藏如何设置它们.
构建器和工厂IMHO之间的关键区别在于,当您需要执行大量操作来构建对象时,构建器非常有用.例如,想象一下DOM.您必须创建大量节点和属性才能获得最终对象.当工厂可以在一个方法调用中轻松创建整个对象时,使用工厂.
使用构建器的一个示例是构建XML文档,我在构建HTML片段时使用了此模型,例如,我可能有一个构建特定类型的表的构建器,它可能具有以下方法(参数未显示):
BuildOrderHeaderRow() BuildLineItemSubHeaderRow() BuildOrderRow() BuildLineItemSubRow()
然后,此构建器将为我吐出HTML.这比通过一个大的程序方法更容易阅读.
查看维基百科上的Builder Pattern.
.NET StringBuilder类是构建器模式的一个很好的例子.它主要用于通过一系列步骤创建字符串.你在ToString()上做的最终结果总是一个字符串,但该字符串的创建因StringBuilder类中使用的函数而异.总而言之,基本思想是构建复杂对象并隐藏其构建方式的实现细节.
对于多线程问题,我们需要为每个线程构建一个复杂的对象.该对象表示正在处理的数据,并且可能根据用户输入而改变.
我们可以用工厂吗?是
我们为什么不呢?我想,生成器更有意义.
工厂用于创建相同基本类型的不同类型的对象(实现相同的接口或基类).
构建器一遍又一遍地构建相同类型的对象,但构造是动态的,因此可以在运行时更改.
当你有很多选择要处理时,你可以使用它.想想像jmock这样的事情:
m.expects(once()) .method("testMethod") .with(eq(1), eq(2)) .returns("someResponse");
它感觉更自然,而且......可能.
还有xml构建,字符串构建和许多其他东西.想象一下,如果java.util.Map
把它作为一个建设者.你可以做这样的事情:
Mapm = new HashMap () .put("a", 1) .put("b", 2) .put("c", 3);
在浏览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; } }
我总是不喜欢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");
现在,我们有了一个简洁的单一类,该类可以管理自己的初始化,并且与生成器几乎一样的工作,除了它的优雅得多。
在之前的答案(双关语)的基础上,一个优秀的现实世界的例子是Groovy的内置支持Builders
.
使用Groovy创建XML MarkupBuilder
使用Groovy创建XML StreamingMarkupBuilder
Swing Builder
SwingXBuilder
见建设者在Groovy的文档
构建器的另一个优点是,如果你有一个Factory,你的代码中仍然会有一些耦合,因为要使Factory工作,它必须知道它可能创建的所有对象.如果添加另一个可以创建的对象,则必须修改工厂类以包含他.这也发生在抽象工厂中.
另一方面,使用构建器,您只需为此新类创建新的具体构建器.导向器类将保持不变,因为它在构造函数中接收构建器.
此外,还有许多口味的建设者.Kamikaze Mercenary的另一个.
////// 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); } }