Setter AND(不是OR或VS)构建器模式

时间:2014-03-14 21:41:28

标签: java design-patterns 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;
  }
}

到目前为止一切顺利。

现在让我们假设一个用例,我需要update cheese。那需要setter。我从未见过一个构建器模式与setter共存的例子,让我怀疑我所做的是反模式。

setter AND builders可以共存吗?

6 个答案:

答案 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)

让我建议另外几个选项,朝着第一个答案的方向前进。如前所述,构建器模式的优点包括能够在多个步骤上积累知识,支持不可变实例,以及确保在一致状态下创建“新鲜”对象。我会留在你问题设计的概念中。

  1. 您可以通过实例方法扩展Pizza类(例如 withCheese(boolean value)返回新的Pizza实例 其他属性与接收实例的属性匹配但是 具有指定的cheese属性值。这保留了 原始的不变性,但给你一个新的实例 预期的差异。

  2. 您可以通过实例方法扩展Pizza类(例如 builder()返回使用。初始化的Pizza.Builder 接收实例的属性。然后所有的方法已经 Pizza.Builder上的build()可用,无需添加实例 每个选项的方法1.该利益的成本是需要制作一个 最终Pizza.Builder致电Pizza pizza0 = new Pizza.Builder(10) .cheese(true) .build();

  3. 执行后

    // 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();

Ref:https://projectlombok.org/features/Builder

答案 5 :(得分:0)

我认为它将来可能会对某人有所帮助,但是复制构造函数呢?

这不会破坏对象不变性的约定,您可以选择使用具有不同属性值的Pizza

因此,您基本上可以使用两种方法:

  1. Pizza.Builder(pizza).cheese(cheese).build()
  2. Pizza.from(pizza).cheese(cheese).build()Akash Srivastava建议

这两种方法基本上都期望使用Pizza的副本构造函数,甚至期望从builder参数属性提供新创建的pizza实例。