【问题标题】:Is this a valid Java implementation of an immutable class and the Builder pattern?这是不可变类和 Builder 模式的有效 Java 实现吗?
【发布时间】:2010-09-13 19:01:05
【问题描述】:

Builder 实现了 Cloneable 并覆盖了 clone() ,而不是复制构建器的每个字段,不可变类保留了构建器的私有克隆。这使得返回新构建器和创建不可变实例的稍微修改的副本变得容易。

我可以这样走

MyImmutable i1 = new MyImmutable.Builder().foo(1).bar(2).build();
MyImmutable i2 = i1.builder().foo(3).build();

据说 Cloneable 接口有些损坏,但是这是否违反了良好的 java 编码习惯,这个结构有什么问题吗?

final class MyImmutable { 
  public int foo() { return builder.foo; }
  public int bar() { return builder.bar; }
  public Builder builder() { return builder.clone(); }
  public static final class Builder implements Cloneable {
    public Builder foo(int val) { foo = val; return this; }
    public Builder bar(int val) { bar = val; return this; }
    public MyImmutable build() { return new MyImmutable(this.clone()); }
    private int foo = 0;
    private int bar = 0;
    @Override public Builder clone() { try { return (Builder)super.clone(); } catch(CloneNotSupportedException e) { throw new AssertionError(); } }
  }
  private MyImmutable(Builder builder) { this.builder = builder; }
  private final Builder builder;
}

【问题讨论】:

    标签: java immutability builder-pattern


    【解决方案1】:

    一般来说,从 Builder 构造的类没有任何关于 builder 的专业知识。也就是说 Immutable 会有一个构造函数来为 foo 和 bar 提供值:

    public final class MyImmutable {
      public final int foo;
      public final int bar;
      public MyImmutable(int foo, int bar) {
        this.foo = foo;
        this.bar = bar;
      }
    }
    

    构建器将是一个单独的类:

    public class MyImmutableBuilder {
      private int foo;
      private int bar;
      public MyImmutableBuilder foo(int val) { foo = val; return this; }
      public MyImmutableBuilder bar(int val) { bar = val; return this; }
      public MyImmutable build() { return new MyImmutable(foo, bar); }
    }
    

    如果您愿意,可以向 MyImmutable 构建器添加一个静态方法以从现有的 MyImmutable 实例开始:

    public static MyImmutableBuilder basedOn(MyImmutable instance) {
      return new MyImmutableBuilder().foo(instance.foo).bar(instance.bar);
    }
    

    【讨论】:

    • 我试图偷工减料并避免明确复制字段。 “basedOn”方法非常清楚,但它需要我(再次)复制字段。可能是我太懒了。
    • 很好的建议 basedOn 方法:)
    【解决方案2】:

    我以前没有见过这种方法,但看起来它可以正常工作。

    基本上,它使构建器模式的实现相对简单,但运行时开销稍高(额外的对象 + 克隆操作 + 访问器函数中可能会或可能不会被编译出来的间接级别)。

    您可能需要考虑的一种潜在变化:如果您使构建器对象本身不可变,则无需防御性地克隆它们。这可能是一个整体的胜利,特别是如果您构建对象的频率比您更改构建器的频率高得多。

    【讨论】:

    • 使构建器不可变是一个有趣的建议,但也许我应该直接效仿 Joshua Blochs 的优秀例子,不要再想聪明了。好吧,结果错了,显然 Joshua Bloch 非常聪明。我的意思是遵循他的优秀著作的第 2 项。
    • 使用上述方法,从不同的不可变对象生成具有任意数量更改的新不可变对象将需要两个防御性副本。如果没有可变构建器,进行 N 次更改将需要 N 个防御性副本。哪种方法更好取决于要进行的更改的数量。
    【解决方案3】:

    您的实现类似于 Josh Bloch 的 Effective Java 2nd Edition 中详述的实现。

    一个争论点是你的build() 方法。如果单个构建器创建一个不可变实例,考虑到它的工作已经完成,允许再次使用该构建器是否公平?这里需要注意的是,即使您正在创建一个不可变对象,构建器的可变性也可能会导致一些相当“令人惊讶”的错误。

    为了纠正这个问题,建议构建方法应该创建实例,然后使构建器无法再次构建对象,需要一个新的构建器。尽管样板文件可能看起来很乏味,但后来获得的好处超过了目前所需的努力。然后,实现类可以接收 Builder 实例作为构造函数参数,但是构建器应该让实例拉出所有需要的状态,释放构建器实例并保留对相关数据的最终引用。

    【讨论】:

    • “Effective Java”指出这是一个好处:“单个构建器可用于构建多个对象。构建器的参数可以在对象创建之间进行调整以改变对象。”
    • 是的。您不希望之前使用构建器干扰它当前用于创建的新对象。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-15
    • 1970-01-01
    • 2018-08-25
    • 2011-03-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多