什么是建造者模式的一些常见且真实的使用示例?它为你带来了什么好处?为什么不只使用工厂模式?
什么是建造者模式的一些常见且真实的使用示例?它为你带来了什么好处?为什么不只使用工厂模式?
以下是一些理由,支持在Java中使用建造者模式及示例代码。但这是《设计模式》中Gang of Four所介绍的建造者模式的实现。你在Java中使用它的原因同样适用于其他编程语言。
正如Joshua Bloch在《Effective 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) { ... }
这被称为Telescoping构造函数模式。 这种模式的问题在于,一旦构造函数有4或5个参数,就会变得难以记住所需的参数顺序以及在给定情况下可能需要哪个特定的构造函数。
你可以使用一个替代方案来避免Telescoping构造函数模式,那就是使用JavaBean模式。在JavaBean模式中,你首先使用必选参数调用构造函数,然后在之后调用任何可选的setter方法:
Pizza pizza = new Pizza(12);
pizza.setCheese(true);
pizza.setPepperoni(true);
pizza.setBacon(true);
问题在于对象是通过多次调用创建的,因此在构建过程中可能处于不一致的状态。这还需要大量额外的工作来确保线程安全。
更好的选择是使用 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;
}
}
请注意,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();
更加简洁明了。 - Fabian SteegPizza.Builder(12).cheese().pepperoni().bacon().build();
,如果你只需要一些胡椒披萨,那么你需要重新编译代码或者有不必要的逻辑。至少你也应该提供类似于@Kamikaze Mercenary最初建议的带参数版本:Pizza.Builder(12).cheese(true).pepperoni(false).bacon(false).build();
。当然,我们从来没有单元测试,是吗? - egallardo考虑一家餐厅。"今日菜品"的创建是工厂模式,因为你告诉厨房“给我今日菜品”,而厨房(工厂)根据隐藏的标准决定生成哪个对象。
如果你点一份自定义比萨,建造者模式就会出现。在这种情况下,服务员告诉厨师(建造者)“我需要一份比萨;加入芝士、洋葱和熏肉!”因此,建造者公开了生成的对象应该具有的属性,但隐藏了如何设置它们。
BuildOrderHeaderRow()
BuildLineItemSubHeaderRow()
BuildOrderRow()
BuildLineItemSubRow()
.NET StringBuilder类是建造者模式的一个很好的例子。它主要用于通过一系列步骤创建字符串。通过调用ToString()方法,您得到的最终结果始终是一个字符串,但该字符串的创建方式取决于StringBuilder类中使用了哪些函数。总之,基本思想是构建复杂对象并隐藏其构建方式的实现细节。
我一直不喜欢生成器模式,因为它往往是笨拙的、显眼的,并且常常被经验不足的程序员滥用。只有当您需要从某些数据中组装对象并需要后初始化步骤(即在收集所有数据后执行某些操作)时,它才有意义。而在99%的情况下,生成器仅用于初始化类成员。
在这种情况下,更好的方法是在类内部声明withXyz(...)
类型的setter方法,并使其返回对自身的引用。
考虑以下示例:
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");
现在我们有一个整洁的单一类,它管理自己的初始化并执行与构建器几乎相同的工作,只是更加优雅。
对于一个多线程的问题,我们需要为每个线程建立一个复杂的对象。该对象表示正在处理的数据,可能根据用户输入而变化。
我们能使用工厂吗?是的。
为什么我们没有用工厂呢?我想建造者更有意义。
工厂用于创建相同基本类型的不同对象(实现相同接口或基类)。
建造者反复构建相同类型的对象,但是构建过程是动态的,因此可以在运行时进行更改。
在学习 Microsoft MVC 框架时,我想到了建造者模式。我在 ControllerBuilder 类中发现了这个模式。该类返回控制器工厂类,然后用于构建具体控制器。
我认为使用建造者模式的好处在于,可以创建自己的工厂并将其插入到框架中。
@Tetha,这里有一个由意大利人经营的餐厅(框架),提供披萨。为了准备披萨,意大利人(对象构建器)使用烤箱(工厂)和披萨底(基类)。
现在印度人接管了这家餐厅,餐厅(框架)提供的是印度煎饼。为了准备煎饼,印度人(对象构建器)使用平底锅(工厂)和面粉(基类)。
如果你看一下这种情况,食物是不同的,准备食物的方式也不同,但是它们在同一个餐厅(同一个框架)内。餐厅应该被建造成可以支持任何菜系,如中国菜、墨西哥菜等。框架内的对象构建器可以插入你想要的菜系,例如:
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;
}
}
当需要处理大量选项时,您可以使用它。考虑像 jmock 这样的东西:
m.expects(once())
.method("testMethod")
.with(eq(1), eq(2))
.returns("someResponse");
感觉更加自然,而且...
还有XML构建、字符串构建等等。想象一下如果java.util.Map
像一个构建器一样具有put方法,你就可以像这样做:
Map<String, Integer> m = new HashMap<String, Integer>()
.put("a", 1)
.put("b", 2)
.put("c", 3);