2

我想实现一个具有流畅界面的构建器。

要求

为了使事情变得更加困难,还有两个额外的要求:

  1. 我希望返回的对象是不可变的,以便它可以按如下方式使用,并且接口应该在派生接口中可扩展:

    ConcreteBuilder b1 = builder().setValue(1);
    ConcreteBuilder b2 = b1.setValue(2);
    
    ComplexObject o1 = b1.build();
    o1.getValue(); // should return 1
    
    ComplexObject o2 = b2.build();
    o2.getValue(); // should return 2
    

    首先是一个有待讨论的问题:构建器是否应该是不可变的,以便它具有上述语义?( JonathanHow to create an immutable builder of an immutable class that contains a set? 中简要评论了这一点? )

  2. 构建器接口应该是可扩展的:

    interface Builder<T extends Builder<T>> {
         T setValue(int v);
    }
    
    interfacte BuilderEx<T extends BuilderEx<T>> {
         T someOtherOpp();
    }
    

    (它使用 Eamonn McManus 1描述的自引用泛型)

执行

为了满足第一个要求,AbstractBuilder 的实现类似于如何创建包含集合的不可变类的不可变构建器?

class AbstractBuilder<T extends Builder<T>> implements Builder<T> {
    T setValue(final int v) {
        return castToConcrete(new AbstractBuilder<T>() {
            // with build() overridden to use v
        }
    }
}

castToConcrete应该类似于self()1getThis()2:将 a 转换Builder<T>ConcreteBuilderConcreteBuilderEx取决于T. 问题是如何实现castToConcrete

由于构建器方法创建新实例,因此覆盖方法getThis不起作用ConcreteBuilder。使用提供给 的构造函数并存储在字段中的“函子” 3可以转换为。添加以下字段(它使用 Guava 库3到:AbstractBuilderBuilder<T>TAbstractBuilder

Function<Builder<T>, T> castToConcrete;

问题

我目前的实施castToConcrete变得非常丑陋:

  1. 这是一if-else棵树instanceof
  2. input instanceof Builder而不是input instanceof BuilderEx需要在 中定义所有方法的包装类时,将这些方法BuilderEx转发Builderinput并为其他方法执行默认或 nil 操作。
  3. input instanceof BuilderEx它将输入转换为ConcreteBuilderEx即使input可能不是实际实例,ConcreteBuilderEx而是匿名子类的实例AbstractBuilderEx(希望这可行。) [由 OP 编辑​​]不起作用:我们需要每个接口的包装器[/编辑]

有什么更好/更清洁的方法来做到这一点?

4

1 回答 1

3

干净的方法是不使用,AbstractBuilder而是为您需要构建的每种类型创建一个特定的构建器。通过使构建器方法可读并针对您尝试构建的对象进行定制,您可以获得流畅的界面。如果您需要创建大量构建器,您可以使用代码生成,但这些方法不会设计得那么好。

于 2012-05-16T13:44:01.843 回答