java - 当你用Builder模式?

  显示原文与译文双语对照的内容

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

时间:

构建器和工厂之间的关键区别是,当你需要做大量的事情来构建一个对象时,构建器很有用。 比如想象一个 DOM 。 你必须创建大量的节点和属性来获取 final 对象。 工厂在工厂可以在一个方法调用中轻松创建整个对象时使用。

使用生成器的一个例子是一个构建一个xml文档,我用这个模型在构建html片段例如我可能有一个构建器来构建一个特定类型的表和它可能有以下方法 ( 参数未显示) :


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

这个构建器将为我吐出 HTML 。 这样更容易阅读,然后通过一个大程序方法进行遍历。

在维基百科编辑器上查看 Builder模式。

以下是一些原因主张的使用模式和示例代码在java中,但它是建造者模式的实现由"四人帮"设计模式。 你在Java中使用它的原因也适用于其他编程语言。

就像Joshua在 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) {.. . }

这就叫做伸缩构造函数模式。 这种模式的问题是,一旦 4或者 5参数构造函数的时间变得很难记住所需参数以及特定的顺序构造器你可能希望在一个给定的情况下。

另一个你必须伸缩式构造函数模式javabean模式你叫强制参数的构造函数,然后调用任何可选的setter后:


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

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

更好的选择是使用生成器模式。


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方法返回的生成器对象链。


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

这使得代码易于编写,易于阅读和理解。 在这个例子中,构建方法可以修改检查参数后,复制从建造到比萨对象和扔illegalstateexception如果提供了一个无效的参数值。 这里模式是灵活的,并且将来可以轻松地向它添加更多参数。 只有当一个构造函数有超过 4个或者 5个参数时,它才真正有用。 说,它可能是值得的在第一时间如果你怀疑你可能会在未来增加更多的参数。

我从这本书很大程度上借这个话题 Effective Java,2nd版约书亚布洛赫。 为了进一步了解这个模式和其他 Effective Java 实践,我强烈推荐它。

考虑一个餐厅。创建的一餐"今天"是工厂模式,因为你告诉厨房"今天给我吃顿饭"和厨房( 工厂) 决定对象生成,基于隐性标准。

如果你订购自定义比萨,则会显示生成器。 在这种情况下,服务员告诉厨师( 生成器)"我需要一个披萨,加上奶酪,洋葱,熏肉"因此,构建器应该公开属性生成的对象,但隐藏了如何设置它们。!

.NET StringBuilder 类是构建器模式的一个很好的例子。 它主要用于在一系列步骤中创建字符串。 final 结果你做 ToString() 始终是一个字符串,但创建 StringBuilder 类中的字符串按照功能的不同。 总而言之,基本思想是构建复杂的对象,并隐藏如何构建它的实现细节。

当你有很多选项可以处理时,你可以使用它。 考虑一下jmock之类的事情:


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

它感觉更自然,而且是- 可能。

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


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

对于multi-threaded问题,我们需要一个复杂对象来为每个线程构建一个复杂的对象。 对象表示正在处理的数据,并且根据用户输入可以更改。

我们可以使用工厂? 是的

为什么不构建器更有意义我猜。

工厂用于创建不同类型的对象,这些对象是相同的基本类型( 实现相同的接口或者基类) 。

构建器反复构建相同类型的对象,但是构造是动态的,所以它可以在运行时更改。

在前面的答案中构建( 双关双关双关),一个优秀的真实示例是构建的,它支持 Builders

看到 建筑商 groovy文档

建造者的另一个优点是,如果你有一个工厂,还有一些耦合在你的代码中,因为工厂工作,它必须知道它可以创建的所有对象 。 如果你添加了可以创建的另一个对象,则必须修改工厂类以包含它。 在抽象工厂也发生这种情况。

另一方面,对于构建器,你只需要为这个新类创建一个新的具体构建器。 director类将保持不变,因为它在构造函数中接收生成器。

还有,有很多风格的生成器。 Kamikaze Mercenary`s提供另一个。


///<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.GetMessage() 就会使用从网络上收集的数据构建消息实例。

...