问题
I know there are many variations and related topics to this one here on stack overflow but I haven't found any compelling answers so I'll give it a go myself.
I'm trying to design a builder factory that returns different subclasses of a common builder interface. I want to allow all the implementations to share a common abstract class for code re-use.
Note that I'm not interested in the return type of the build()
method, only what types the builders are.
This is what I have so far:
Builder interface with generic for the sub-interfaces:
interface FruitBuilder<T extends FruitBuilder<T>> {
T taste(String taste);
T shape(String shape);
T weight(String weight);
Fruit build();
}
Some builders have additional methods:
interface GrapesBuilder extends FruitBuilder<GrapeBuilder> {
GrapesBuilder clusterSize(int clusterSize);
}
Next is to specify a factory that returns the specific builders:
interface FruitBuilderFactory {
GrapesBuilder grapes();
AppleBuilder apple();
LemonBuilder lemon();
}
A user of these interfaces should be able to use it like:
Fruit grapes = fruitBuilderFactory
.grapes()
.weight(4)
.color("Purple")
.clusterSize(4) // Note that the GrapesBuilder type must be accessible here!
.build();
Most of the logic would go into the abstract class, including advanced build logic:
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();
}
Given the base class, it's really simple to implement new builders:
class GrapseBuilderImpl extends BaseFruitBuilder<GrapesBuilder> {
int clusterSize;
GrapesBuilder clusterSize(int clusterSize) {
this.clusterSize = clusterSize;
}
protected Fruit createSpecificInstance() {
return new Grape(clusterSize);
}
}
This is all compiling and fine (at least my real code). The question if whether or not I can remove the ugly cast to T in the abstract class.
回答1:
One option to avoid casting is to define a single abstract method returning 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;
}
}
The downside is that you have to trust each subclass to implement the method correctly. Then again, with your approach, there's nothing stopping anyone from declaring a subclass GrapesBuilder extends BaseFruitBuilder<AppleBuilder>
, so you'll need to trust subclasses to some extent.
EDIT Just realized this solution was referenced by @user158037's comment. I've used this myself, but never realized it was a known idiom. :-)
回答2:
You're using what are commonly referred to as self-types, but seem to be somewhat muddling what T
refers to. You have a FruitBuilder
interface that has a generic type T
, which should represent the type the builder will return. Instead you seem to be using it to represent the type of the builder itself, which likely isn't necessary (if it is, see below for a more complex suggestion).
Be careful and intentional with generics; the fact that they are abstract concepts makes them easy to confuse. In your case I would suggest the following interface:
interface FruitBuilder<F extends Fruit> {
FruitBuilder<F> taste(...);
FruitBuilder<F> shape(...);
FruitBuilder<F> weight(...);
F build();
}
Individual builders now declare the type they will eventually build, rather than their own type:
interface FruitBuilderFactory {
GrapesBuilder grapes(); // define a concrete subtype to add methods
FruitBuilder<Apple> apple();
}
And now every FruitBuilder
clearly is a builder of F
instances, and each of your builder methods can cleanly return this
and your build()
method will return an object of the expected generic type, letting you write something like:
Grape grape = FruitBuilderFactory.grape()....build();
For most sub-class builder patterns this is all you need. Even if you need to define additional builder methods for certain types you can still use this structure. Consider Guava's ImmutableCollection.Builder<E>, and the ImmutableMultiset.Builder<E> subtype that extends it and provides additional methods. Notice also that they associate the builder directly with the type, rather than in a common builder-factory class. Consider replicating that structure yourself (e.g. Grape.Builder
, Apple.Builder
, etc.).
In rare cases you do need to use self-types and represent the builder as a generic type as well. This creates a complex type structure but does show up in practice in certain places such as Truth's Subject<S extends Subject<S,T>,T> (which has significantly more complex semantics than most builders). Notice that it has two generics; S
represents itself while T
represents the type actually being worked with. If your FruitBuilder
needed to be this complex you'd use a similar pattern:
interface FruitBuilder<B extends FruitBuilder<B, F>, F> {
B taste(...);
B shape(...);
B weight(...);
F build();
}
来源:https://stackoverflow.com/questions/32849499/builder-factory-returning-different-sub-interfaces