什么时候使用构建器模式?

什么是常见的现实世界的例子的使用生成器模式?它能给你买什么?为什么不直接使用工厂模式?

268748 次浏览

当你有很多选择要处理的时候,你就用它。想想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);

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

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

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

然后这个构建器会为我输出HTML。这比遍历一个大型过程方法容易得多。

看看Wikipedia上的构建器模式

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

我们能用工厂代替吗?是的

为什么我们没有呢?我想建造者更有意义。

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

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

在前面的回答(双关语)的基础上,一个很好的现实例子是Groovy内置了对Builders的支持。

参见Groovy文件中的建筑商

我在自制消息库中使用了builder。库核心从线路接收数据,用Builder实例收集数据,然后,一旦Builder确定它已经获得了创建Message实例所需的一切,Builder. getmessage()就使用从线路收集的数据构造消息实例。

以餐厅为例。“今天的饭菜”的创建是一种工厂模式,因为您告诉厨房“给我今天的饭菜”,然后厨房(工厂)根据隐藏的标准决定生成什么对象。

如果你订购了一个定制的披萨,建造者就会出现。在这种情况下,服务员告诉厨师(建筑工人)“我需要一个披萨;加奶酪、洋葱和培根!”因此,构建器公开了生成的对象应该具有的属性,但隐藏了如何设置这些属性。

. net StringBuilder类是构建器模式的一个很好的例子。它主要用于在一系列步骤中创建字符串。执行ToString()的最终结果始终是一个字符串,但该字符串的创建取决于使用StringBuilder类中的哪些函数。总而言之,基本思想是构建复杂的对象,并隐藏如何构建对象的实现细节。

下面是在Java中使用模式和示例代码的一些理由,但它是由设计模式中四人组介绍的构建器模式的实现。在Java中使用它的原因也适用于其他编程语言。

正如Joshua Bloch在有效的Java,第二版中所述:

当设计那些构造函数或静态工厂具有多个参数的类时,构建器模式是一个很好的选择。

我们都在某些时候遇到过一个类,它有一个构造函数列表,其中每次添加都会添加一个新的选项形参:

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模式,在这里你调用一个带有强制参数的构造函数,然后在下面调用任何可选的设置:

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

注意Pizza是不可变的,参数值都在一个位置. #。因为Builder的setter方法返回Builder对象,所以它们是能被拴住

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

在这个例子中,可以修改构建方法用于在参数从构建器复制到Pizza对象后检查参数,如果提供了无效的参数值,则抛出IllegalStateException。此模式很灵活,将来很容易添加更多参数。只有当构造函数的参数超过4或5个时,它才有用。也就是说,这可能是值得摆在第一位的如果您怀疑将来可能会添加更多参数。

关于这个话题,我从Joshua Bloch的书有效的Java,第二版中借鉴了很多。要了解关于此模式和其他有效Java实践我强烈推荐它。的更多信息

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

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

此外,构建器也有很多种。神风特攻队雇佣兵又给了一个。

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

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

@Tetha,可以有一家意大利人开的餐厅(框架),供应披萨。为了准备披萨,Italian guy (Object Builder)使用Owen (Factory)和一个披萨基(基类)。

现在印度人从意大利人手里接管了餐厅。印度餐厅(框架)提供dosa而不是披萨。为了准备dosa, Indian guy(对象构建器)使用fried Pan(工厂)和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;
}
}

当我想在Java中为XML使用标准XMLGregorianCalendar来对DateTime进行对象封送时,我听到了很多关于使用它是多么沉重和麻烦的评论。我试图控制xs:datetime结构中的XML字段来管理时区,毫秒等。

因此,我设计了一个实用程序,从GregorianCalendar或java.util.Date构建一个xml公历。

由于我工作的地方,我不允许在网上分享它,但这里有一个客户如何使用它的例子。它抽象了XMLGregorianCalendar的细节,并过滤了xs:datetime中较少使用的一些实现。

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

假定此模式更像是一个过滤器,因为它将xmlCalendar中的字段设置为未定义,因此将它们排除在外,但它仍然“构建”它。我已经很容易地向构建器添加了其他选项,以创建xs:date和xs:time结构,并在需要时操作时区偏移量。

如果您曾经见过创建和使用XMLGregorianCalendar的代码,您就会看到这是如何使它更容易操作的。

/// <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模式,因为它笨拙、突兀,而且经常被缺乏经验的程序员滥用。这是一个模式,只有当你需要从一些需要初始化后步骤的数据中组装对象时才有意义(即一旦收集了所有的数据-对它做一些事情)。相反,在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");

现在我们有了一个整洁的单一类,它管理自己的初始化,做的工作与构建器几乎相同,只是它要优雅得多。