Java generic builder
Asked Answered
M

4

11

Suppose I need some DerivedBuilder to extend some BaseBuilder. Base builder has some method like foo (which returns BaseBuilder). Derived builder has method bar. Method bar should be invoked after method foo. In order to do it I can override foo method in DerivedBuilder like this:

@Override
public DerivedBuilder foo() {
    super.foo();
    return this;
}

The problem is that BaseBuilder has a lot of methods like foo and I have to override each one of them. I don't want to do that so I tried to use generics:

public class BaseBuilder<T extends BaseBuilder> {
    ...

    public T foo() {
        ...
        return (T)this;
    }
}

public class DerivedBuilder<T extends DerivedBuilder> extends BaseBuilder<T> {
    public T bar() {
        ...
        return (T)this;
    }
}

But the problem is that I still can not write

new DerivedBuilder<DerivedBuilder>()
        .foo()
        .bar()

Even though T here is DerivedBuilder. What can I do in order to not to override a lot of functions?

Macadam answered 18/8, 2016 at 10:14 Comment(5)
With this approch of generics, you can call : new DerivedBuilder<DerivedBuilder>().foo().bar(). It will work and execute foo first and then bar. If you want to call more methods of BaseBuilder and lastly you want to call DerivedBuilder method, then it's not possible, because second time, method returns reference of BaseBuilder, with this you can't call DerivedBuilder's method.Mccabe
@Sandeep.K I tried to execute that and compiler complains that bar was not defined in BaseBuilder because then I do .foo().bar() the only thing compiler knows about T after executing foo is that this T extends BaseBuilderMacadam
See this and this and this...Leucine
@Leucine I don't see how this solves my problem. In your links described some ways to ensure that super method was called when my question is about how not to call it explicitly.Macadam
They address the same problem and (some of) the answers there answer your question. There is no contradiction between "some ways to ensure that super method was called" and "how not to call it explicitly" - ensuring it's called can, in some way, be done not explicitly, which is what the answers there show.Leucine
B
8

Your problem is the definition of DerivedBuilder:

class DerivedBuilder<T extends DerivedBuilder>;

And then instantiating it with a type erased argument new DerivedBuilder<DerivedBuilder<...what?...>>().

You'll need a fully defined derived type, like this:

public class BaseBuilder<T extends BaseBuilder<T>> {
    @SuppressWarnings("unchecked")
    public T foo() {
        return (T)this;
    }
}

public class DerivedBuilder extends BaseBuilder<DerivedBuilder> {
    public DerivedBuilder bar() {
        return this;
    }
}

Check ideone.com.

Bandicoot answered 18/8, 2016 at 12:32 Comment(2)
Is there also a possibility to derive the DerivedBuilder further, for example SubDerivedBuilder? SubDerivedBuilderM<...> extends DerivedBuilder<...>Gwynethgwynne
@MCEmperor yes, but you need to make all the most derived types pass complete types to their base class. Check this forkBandicoot
G
5

In addition to BeyelerStudios's answer, if you want to nest further, you can just use this:

class Base<T extends Base<?>> {
    public T alpha() { return (T) this; }
    public T bravo() { return (T) this; }
    public T foxtrot() { return (T) this; }
}

class Derived<T extends Derived<?>> extends Base<T> {
    public T charlie() { return (T) this; }
    public T golf() { return (T) this; }
}

class FurtherDerived<T extends FurtherDerived<?>> extends Derived<T> {
    public T delta() { return (T) this; }
    public T hotel() { return (T) this; }
}

class MuchFurtherDerived<T extends MuchFurtherDerived<?>> extends FurtherDerived<T> {
    public T echo() { return (T) this; }
}

public static void main(String[] args) {
    new MuchFurtherDerived<MuchFurtherDerived<?>>()
        .alpha().bravo().charlie().delta().echo().foxtrot().golf().hotel()
        .bravo().golf().delta().delta().delta().hotel().alpha().echo()
        .echo().alpha().hotel().foxtrot();
}
Gwynethgwynne answered 18/8, 2016 at 13:29 Comment(0)
M
2

Instead of casting return (T) this; I here did a Class.cast(this).

To realize:

BaseBuilder.build(ExtendedBuilder.class).foo().bar().foo().bar();

Every class in the hierarch needs to know the actual final child class, hence I chose to make a factory method build in the base class.

The cast of this to the actual child is done in a final method of the base class too, providing return me();.

class BaseBuilder<B extends BaseBuilder<B>> {

    protected Class<B> type;

    public static <T extends BaseBuilder<T>> T build(Class<T> type) {
        try {
            T b = type.newInstance();
            b.type = type;
            return b;
        } catch (InstantiationException | IllegalAccessException e) {
            throw new IllegalStateException(e);
        }
    }

    protected final B me() {
        return type.cast(this);
    }

    B foo() {
        System.out.println("foo");
        return me();
    }
}

class ExtendedBuilder extends BaseBuilder<ExtendedBuilder> {

    ExtendedBuilder bar() {
        System.out.println("bar");
        return me();
    }
}
Multifaceted answered 18/8, 2016 at 13:26 Comment(2)
And now you have a Builder Factory. Also, the type field and the type.cast(this) is pointless, because it doesn't provide any additional compile-time safety and complicates the entire thing.Preprandial
@XetraSu proposed static class which probably fits the usage of them.Multifaceted
A
0

What I understand from your question is that the method foo() should be executed before method bar().
If that is correct, you can apply the Template Design Pattern.
Create a abstract method bar in the BaseBuilder.
And a new method say 'template'. The method template will define the sequence- first foo() is executed followed by bar().
DerivedBuilder will provide the implementation for the method bar.

public abstract class BaseBuilder {

    public void foo(){
        System.out.println("foo");
    }

    public abstract void bar();

    public void template(){
        foo();
        bar();
    }
}

public class DerivedBuilder extends BaseBuilder{

    @Override
    public void bar() {
        System.out.println("bar");      
    }

    public static void main(String[] args) {
        BaseBuilder builder = new DerivedBuilder();
        builder.template();
    }
}


Hope this helps.

Aeolian answered 18/8, 2016 at 12:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.