Setter AND(不是OR或VS)构建器模式
我有一种情况,我使用构建器模式来构造一个对象。 最好的例子是披萨代码
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。 但是,如果它是可变的并且具有setter,为什么不使用简单的构造函数构建对象,并调用setter来改变状态? 除非只有一两个字段是可变的,否则构建器不再具有实用性。
现在让我们假设我需要
update
cheese
用例。 这需要一个setter
。
而不是考虑setter或构建器 ,尝试考虑提供给类的用户的类和服务的职责。
你在这里称之为setter的只是一个转换对象的服务。 构建器是一种创建复杂对象的服务。
如果您要提供setter来访问属性(或者应该对客户端保密的复杂对象的详细信息),那么您将打破封装。 这是一种反模式。 你的奶酪例子不足以揭示为什么那可能是坏事。 用户是否需要知道披萨有奶酪并且能够修改它?
正如JB Nizet所说,没有理由不能存在这两种服务 ,但我会问一个问题,即揭示细节是否合适。
您可能没有看到使用setter的构建器模式,因为您的源可能与GoF发布的示例紧密相关。 构建器模式可以有许多变体,对于任何设计模式都是如此。 构建器模式显然是一种创建模式,但构建器模式的主要目的是解决伸缩构造的问题。 当您需要以非常可控和渐进的方式进行构造时,它也是一种很好的模式。 我刚刚提到的这些东西都没有通过设置者而失效。 模式的一个有用变体是具有setter(通过构建器),其允许准备对象状态,然后具有构建/实例化目标对象的构建方法。 构建方法逐步创建并可能validation最终对象。 以下面的例子为例:
Pizza pizza = pizzaBuilder.newBuilder().addCheese().addPepperoni().addBacon().Build();
上面的示例充分利用了使用Java语言结构的构建器模式。 实际上这很常见。
让我建议另外几个选项,朝着第一个答案的方向前进。 正如已经提到的,构建器模式的优点包括能够在多个步骤上积累知识,支持不可变实例,以及确保在一致状态下创建“新鲜”对象。 我会留在你问题设计的概念中。
-
您可以通过实例方法扩展
Pizza
类(例如withCheese(boolean value)
,它返回一个新的Pizza
实例,其其他属性与接收实例的属性匹配,但具有指定的cheese
属性值。这样可以保留原始的不变性,但是会给你具有预期差异的新实例。 -
您可以通过实例方法扩展
Pizza
类(例如,builder()
返回使用接收实例的属性初始化的Pizza.Builder
。然后,Pizza.Builder
上已有的所有方法都可用,无需为每个选项添加实例方法这个好处的代价是需要在Pizza.Builder
上进行最终的build()
调用。
所以执行后
Pizza pizza0 = new Pizza.Builder(10) .cheese(true) .build();
要获得一个10英寸的芝士披萨,你可以执行
// option 1 Pizza pizza1 = pizza0.withPepperoni(true);
通过选项1获得10英寸奶酪和意大利辣香肠比萨饼
// option 2 Pizza pizza2 = pizza0.builder().pepperoni(true).build();
通过选项2获得相同的东西。
选项1更短,以获得具有单一差异的新披萨,但需要更多努力来实现所有所需的实例方法并构建更多中间披萨以产生多种差异。
选项2总是获得Pizza.Builder
但随后重用其所有function,以获得所需配置的单个结果披萨。 此选项还允许更轻松地添加更多披萨属性(通过将实例属性添加到Pizza
以及将单个相应方法添加到Pizza.Builder
。