Joshua Bloch的Builder模式和PMD警告

5

我使用Joshua Bloch的Builder模式编写了一个类,类似于这个Pizza示例:

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

但是PMD报告了2个警告:

  1. (指向方法Builder.build()) 避免从构造函数类外部通过私有构造函数实例化。通过私有构造函数从构造函数类外部进行实例化通常会导致生成访问器。工厂方法或取消将构造函数设置为私有可以消除此情况。生成的类文件实际上是一个接口。它使访问类能够调用一个新的隐藏包范围构造函数,该构造函数将接口作为补充参数。这将私有构造函数有效地转换为具有包范围的构造函数,并且很难识别。
  2. 类无法实例化并且不提供任何静态方法或字段。具有私有构造函数且没有任何静态方法或字段的类不能使用。

我应该忽略这些警告吗?

另一个问题:类 PizzaBuilder 中的私有字段是重复的。当私有字段的数量增加时,这将变得很麻烦。有没有什么方法可以避免这种情况?


你可以将Pizza构造函数定义为包保护,这样也可以解决第二个问题。 - Rom1
9
这个模式很好,只是PMD太蠢了。我个人会不理会它。 - skaffman
只是一点小提示:我已经使用了一个叫做Make-It-Easy的微型框架有一段时间了,它可以帮助构建这些builders,而且代码非常易读。我主要用它来进行单元测试,但它绝对可以用于生产代码。 - Augusto
我会忽略第一个PMD警告。然而,第二个警告是有道理的。你在问题中提到的Pizza类是无法使用的,因为它没有任何方法。加入一些getter方法,PMD就不会再抱怨了(希望如此)。 - JB Nizet
2个回答

2
关于如何删除重复内容。
我可能会得到更多的“踩”,但也许可以像这样做?
class Pizza {
private int size;
private boolean cheese;
private boolean pepperoni;
private boolean bacon;

public static class Builder {
    private Pizza pizza = new Pizza();

    public Builder(int size) {
        pizza.size = size;
    }

    public Builder cheese(boolean value) {
        pizza.cheese = value;
        return this;
    }

    public Builder pepperoni(boolean value) {
        pizza.pepperoni = value;
        return this;
    }

    public Builder bacon(boolean value) {
        pizza.bacon = value;
        return this;
    }

    public Pizza build() {
        return pizza;
    }
}

private Pizza() {
}
}

7
我不喜欢这个。在调用build()之后,您可以通过创建它的Builder更改Pizza上的字段。建造者模式最好的用途之一是简化不可变类的创建。 - Kenny
抱歉,但是使用这种方式仍然会出现以下问题:1)“避免在构造函数类外部通过私有构造函数实例化。”和2)“该类无法实例化并且不提供任何静态方法或字段”,因此问题没有得到解决。 - aloplop85

1
类Pizza和Builder中的私有字段是重复的。当私有字段数量增加时,这将会很烦人。有没有什么方法可以避免这种情况?
我个人通过使用第三个私有静态值对象类来解决这个问题,该类包含所有字段,并在构建器和主类中使用它(字段访问由委托处理)。当然,这可能会增加行数/类数,但在构建器具有大量字段和检查的情况下非常有价值。
此外,在Pizza类上提供一个静态方法来构建具有强制性字段的Pizza对象也不会有任何损失。除非您不确定强制性字段是什么或者担心强制性字段可能在类演变过程中发生更改。重点是,只要您经过深思熟虑并能够证明自己的行为(就像Joshua Bloch所说的那样),您可以安全地忽略那些警告,因为您知道自己在做什么。 :-)
一个临时代码片段:
public class Pizza {

    private final PizzaVO vo;

    private static class PizzaVO {

        int size;

        boolean cheese;

        boolean pepperoni;

        boolean bacon;
    }

    public static class Builder {

        private final PizzaVO vo = new PizzaVO();

        public Builder(int size) {
            vo.size = size;
        }

        public Builder cheese(boolean value) {
            vo.cheese = value;
            return this;
        }

        public Builder pepperoni(boolean value) {
            vo.pepperoni = value;
            return this;
        }

        public Builder bacon(boolean value) {
            vo.bacon = value;
            return this;
        }

        public Pizza build() {
            return new Pizza(vo);
        }
    }

    private Pizza(PizzaVO vo) {
        this.vo = vo;
    }

    public int getSize() {
        return vo.size;
    }

    // other getter setter methods as per your taste

}

2
这种风格不值得使用。问题并没有得到解决。 - aloplop85
嗨aloplop85,你能澄清一下哪些问题没有解决以及为什么吗? - sudocoder
@sudocoder 至少 PMD 错误“避免在构造函数类外部通过私有构造函数实例化”仍然存在。 - Shannon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接