我有一种情况,我使用构建器模式来构造对象。最好的例子是披萨代码
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;
}
}
到目前为止一切顺利。
现在让我们假设一个用例,我需要update
cheese
。那需要setter
。我从未见过一个构建器模式与setter共存的例子,让我怀疑我所做的是反模式。
setter AND builders可以共存吗?
答案 0 :(得分:14)
您从未见过使用过,因为大多数情况下,构建器模式用于构建不可变对象。
但我不明白为什么他们不能共存。构建器构建一个对象,并且您希望构建的对象是可变的,然后它可以有setter。但是,如果它是可变的并且具有setter,为什么不使用简单的构造函数构建对象,并调用setter来改变状态?除非只有一两个字段是可变的,否则构建器不再有用。
答案 1 :(得分:3)
现在让我们假设一个用例,我需要
update
cheese
。这需要setter
。
不要考虑 setters 或构建器,而应该考虑提供给类的用户的类和服务的职责。
你在这里所谓的setter只是一个转换对象的服务。构建器是一种创建复杂对象的服务。
如果您要提供setter来访问属性(或者应该对客户端保密的复杂对象的详细信息),那么您就打破了封装。这是一种反模式。你的奶酪例子不足以揭示为什么那可能是坏事。用户是否需要知道披萨有奶酪并且能够修改它?
正如JB Nizet所说,没有理由不存在服务,但我会问一个问题,即揭示细节是否合适。
答案 2 :(得分:1)
您可能没有看到使用setter的构建器模式,因为您的源可能与GoF发布的示例紧密相关。构建器模式可以有许多变体,对于任何设计模式都是如此。构建器模式显然是一种创建模式,但构建器模式的主要目的是解决伸缩构造的问题。当您需要以非常可控和渐进的方式进行构建时,它也是一种很好的模式。我刚刚提到的这些东西都没有通过设置者而失效。模式的一个有用变体是具有setter(通过构建器),其允许准备对象状态,然后具有构建/实例化目标对象的构建方法。构建方法逐步创建并可能验证最终对象。以下面的例子为例:
Pizza pizza = pizzaBuilder.newBuilder().addCheese().addPepperoni().addBacon().Build();
上面的示例充分利用了使用Java语言结构的构建器模式。它实际上非常普遍。
答案 3 :(得分:1)
让我建议另外几个选项,朝着第一个答案的方向前进。如前所述,构建器模式的优点包括能够在多个步骤上积累知识,支持不可变实例,以及确保在一致状态下创建“新鲜”对象。我会留在你问题设计的概念中。
您可以通过实例方法扩展Pizza
类(例如
withCheese(boolean value)
返回新的Pizza
实例
其他属性与接收实例的属性匹配但是
具有指定的cheese
属性值。这保留了
原始的不变性,但给你一个新的实例
预期的差异。
您可以通过实例方法扩展Pizza
类(例如
builder()
返回使用。初始化的Pizza.Builder
接收实例的属性。然后所有的方法已经
Pizza.Builder
上的build()
可用,无需添加实例
每个选项的方法1.该利益的成本是需要制作一个
最终Pizza.Builder
致电Pizza pizza0 = new Pizza.Builder(10)
.cheese(true)
.build();
。
执行后
// option 1
Pizza pizza1 = pizza0.withPepperoni(true);
要获得一个10英寸的芝士披萨,你可以执行
// option 2
Pizza pizza2 = pizza0.builder().pepperoni(true).build();
通过选项1或
获得10英寸奶酪和意大利辣香肠比萨饼Pizza.Builder
通过选项2获得相同的东西。
选项1更短,以获得一个单一差异的新披萨,但需要更多的努力来实现所有需要的实例方法,并建立更多的中间比萨饼,以产生多重差异。
选项2始终获得Pizza
,但随后重新使用其所有功能,以便在所需配置中获得单个结果披萨。此选项还允许更轻松地添加更多披萨属性(通过将实例属性添加到Pizza.Builder
并将单个对应方法添加到2 GUI (handle just user GUI request) Tomcat nodes separate jvms
2 processing (background processing) Tomcat nodes separate jvms
。
答案 4 :(得分:1)
您可以使用@Builder(toBuilder = true)。
@Builder(toBuilder = true)
class Pizza {
int val;
}
Pizza ob = Pizza.builder().val(10)build();
Pizza newObj = ob.toBuilder().val(11).build();
答案 5 :(得分:0)
我认为它将来可能会对某人有所帮助,但是复制构造函数呢?
这不会破坏对象不变性的约定,您可以选择使用具有不同属性值的Pizza
。
因此,您基本上可以使用两种方法:
Pizza.Builder(pizza).cheese(cheese).build()
Pizza.from(pizza).cheese(cheese).build()
如Akash Srivastava建议这两种方法基本上都期望使用Pizza
的副本构造函数,甚至期望从builder
参数属性提供新创建的pizza
实例。