Builder Factory返回不同的子接口

时间:2015-09-29 16:44:33

标签: java generics factory builder

我知道这里的堆栈溢出有很多变化和相关的主题,但我还没有找到任何令人信服的答案,所以我自己就试试。

我试图设计一个返回公共构建器接口的不同子类的构建器工厂。我想允许所有实现共享一个公共抽象类,以便重用代码。

请注意,我对build()方法的返回类型不感兴趣,只对构建器的类型感兴趣。

这是我到目前为止所做的:

与子接口的泛型构建器接口:

interface FruitBuilder<T extends FruitBuilder<T>> {
    T taste(String taste);
    T shape(String shape);
    T weight(String weight);

    Fruit build();
}

有些构建者还有其他方法:

interface GrapesBuilder extends FruitBuilder<GrapeBuilder> {
    GrapesBuilder clusterSize(int clusterSize);
}

接下来是指定一个返回特定构建器的工厂:

interface FruitBuilderFactory {
    GrapesBuilder grapes();
    AppleBuilder apple();
    LemonBuilder lemon();
}

这些界面的用户应该能够像以下一样使用它:

 Fruit grapes = fruitBuilderFactory
    .grapes()
    .weight(4)
    .color("Purple")
    .clusterSize(4)  // Note that the GrapesBuilder type must be accessible here!
    .build();

大多数逻辑都会进入抽象类,包括高级构建逻辑:

abstract class BaseFruitBuilder<T extends FruitBuilder<T>> implements FruitBuilder<T> {

   String taste;

   T taste(String taste) {
       this.taste = taste;
       return (T)this;     // Ugly cast!!!!!
   }

   ...

    Fruit build() {
       Fruit fruit = createSpecificInstance();

       // Do a lot of stuff on the fruit instance.

       return fruit;
    }

    protected abstract Fruit createSpecificInstance();
}

鉴于基类,实现新构建器非常简单:

class GrapseBuilderImpl extends BaseFruitBuilder<GrapesBuilder> {
   int clusterSize;
   GrapesBuilder clusterSize(int clusterSize) {
       this.clusterSize = clusterSize;
   }

   protected Fruit createSpecificInstance() {
       return new Grape(clusterSize);
   }
}

这都是编译和罚款(至少我的真实代码)。问题是我是否可以在抽象类中删除丑陋的演员。

2 个答案:

答案 0 :(得分:1)

您正在使用通常称为self-types的内容,但似乎有点混淆了T所指的内容。您有一个FruitBuilder接口,其泛型类型为T,其中表示构建器将返回的类型。相反,您似乎使用它来表示构建器本身的类型,这可能不是必需的(如果是,请参阅下面的更复杂的建议)。

用仿制药小心谨慎;它们是抽象概念的事实使它们容易混淆。在你的情况下,我会建议以下界面:

interface FruitBuilder<F extends Fruit> {
  FruitBuilder<F> taste(...);
  FruitBuilder<F> shape(...);
  FruitBuilder<F> weight(...);
  F build();
}

单个构建器现在声明它们最终将构建的类型,而不是它们自己的类型:

interface FruitBuilderFactory {
  GrapesBuilder grapes(); // define a concrete subtype to add methods
  FruitBuilder<Apple> apple();
}

现在每个FruitBuilder显然都是F个实例的构建器,并且每个构建器方法都可以干净地返回this,并且您的build()方法将返回一个期望的泛型类型,让你写一些类似的东西:

Grape grape = FruitBuilderFactory.grape()....build();

对于大多数子类构建器模式,这就是您所需要的。即使您需要为某些类型定义其他构建器方法,您仍然可以使用此结构。考虑Guava的ImmutableCollection.Builder<E>,以及扩展它的ImmutableMultiset.Builder<E>子类型,并提供其他方法。另请注意,它们将构建器直接与类型相关联,而不是在公共构建器工厂类中。请考虑自己复制该结构(例如Grape.BuilderApple.Builder等。)

在极少数情况下,您确实需要使用自我类型,并将构建器表示为泛型类型。这会创建一个复杂的类型结构,但在某些地方确实显示出来,例如TruthSubject<S extends Subject<S,T>,T>(其语义比大多数构建器复杂得多)。请注意,它有两个泛型; S表示自己,而T表示实际使用的类型。如果你的FruitBuilder需要这么复杂,你会使用类似的模式:

interface FruitBuilder<B extends FruitBuilder<B, F>, F> {
  B taste(...);
  B shape(...);
  B weight(...);
  F build();
}

答案 1 :(得分:1)

避免强制转换的一个选项是定义一个返回T的抽象方法:

abstract class BaseFruitBuilder<T extends FruitBuilder<T>> implements FruitBuilder<T> {

    String taste;

    T taste(String taste) {
       this.taste = taste;
       return returnThis();
    }

    protected abstract T returnThis();

     //...
}

class GrapseBuilderImpl extends BaseFruitBuilder<GrapesBuilder> {
    //...
    @Override
    protected T returnThis() {
        return this;
    }
}

缺点是您必须信任每个子类才能正确实现该方法。然后,再次,使用您的方法,没有什么能阻止任何人声明子类GrapesBuilder extends BaseFruitBuilder<AppleBuilder>,所以您需要在某种程度上信任子类。

编辑刚刚意识到此解决方案已被@ user158037的comment引用。我自己用过这个,但从未意识到这是一个已知的成语。 : - )