Search code examples
javagenericsfactorybuilder

Builder Factory returning different sub-interfaces


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.


Solution

  • 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. :-)