如何使用具有不可变结果和扩展接口的fluent接口实现构建器模式?

时间:2012-05-16 13:22:33

标签: java design-patterns generics builder fluent-interface

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

要求

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

  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
    

    首先讨论一个问题:构建器是否应该是不可变的,以便它具有以上语义? (Jonathan

  2. 中的How to create an immutable builder of an immutable class that contains a set?对其进行了简要评论
  3. 构建器界面应该是可扩展的:

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

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

  4. 实施

    为了满足第一个要求,AbstractBuilder的实现类似于How to create an immutable builder of an immutable class that contains a set?中的实现:

    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() 1 getThis() 2 类似:转换为Builder<T>ConcreteBuilderConcreteBuilderEx取决于T。问题是如何实施castToConcrete

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

    Function<Builder<T>, T> castToConcrete;
    

    问题

    我目前对castToConcrete的实施变得相当丑陋:

    1. 它是if-else
    2. 上的instanceof
    3. input instanceof Builder而不是input instanceof BuilderEx需要在BuilderEx中定义所有方法的包装类时,将Builder中的方法转发到input并执行其他人的默认或无操作。
    4. input instanceof BuilderEx将输入转换为ConcreteBuilderEx时,即使input可能不是ConcreteBuilderEx的实际实例,而是AbstractBuilderEx的匿名子类的实例1}}(希望这有效。) [由OP编辑]不起作用:我们需要为每个接口提供一个包装器[/ edit]
    5. 有什么更好/更清洁的方法呢?

1 个答案:

答案 0 :(得分:3)

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