3

Here we have multiple custom query-DSLs that use related grammar. I am creating to create an AbstractBuilder so that all the commonality can be written in one place. The problem is that is causes issues with method-chaining. When I try to chain from a method in to AbstractBuilder so a subclass, it doesn't work without casting.

With these classes:

class AbstractBuilder{
    protected final StringBuilder bldr = new StringBuilder();

    AbstractBuilder addValue( String name, String value ){
        bldr.append( name ).append( '=' ).append( value )append( ',' );
        return this;
    }

    String toString(){
        return bldr.toString();
    }
}

class IntBuilder extends AbstractBuilder{

    IntBuilder addValue( String name, int value ){
        bldr.append( name ).append( '=' ).append( value )append( ',' );
        return this;
    }
}

This works

new IntBuilder().addValue( "age", 12 ).addValue( "name", "Bruce" ).toString();` but `new IntBuilder().addValue( "name", "Bruce" ).addValue( "age", 12 ).toString();

doesn't unless you make an ugly cast like:

((IntBuilder) (new IntBuilder().addValue( "name", "Bruce" ))).addValue( "age", 12 ).toString();

Now I guess I could override each methods and implement them with calls to their parents (via super.addValue( name, value );), but that is really ugly.

How else can I get every method to return the current class and not the class on which it was defined?

4

2 に答える 2

7

You could achieve this using generics.

class BaseBuilder<B extends BaseBuilder>{
    protected final StringBuilder bldr = new StringBuilder();
    private Class<B> builderImplementationClass;

    protected BaseBuilder( Class<B> builderImpl ){
        builderImplementationClass = builderImpl;

        if( this.getClass() != builderImpl )
            throw new IllegalStateException( "Should match." );
    }

    B addValue( String name, String value ){
        bldr.append( name ).append( '=' ).append( value )append( ',' );
        return builderImplementationClass.cast( this );
    }

    String toString(){
        return bldr.toString();
    }
}

class IntBuilder extends AbstractBuilder<IntBuilder>{

    public IntBuilder(){
        super( IntBuilder.class );
    }

    IntBuilder addValue( String name, int value ){
        bldr.append( name ).append( '=' ).append( value )append( ',' );
        return this;
    }
}

Basically you make all your methods return a value of type B and make sure that B is set to your current Implementation

于 2012-12-14T21:38:29.233 に答える
1

ArtB's solution is good, but you can make it a bit simpler with the following:

private static class AbstractBuilder <T extends AbstractBuilder<T>> {
    protected StringBuilder bldr = new StringBuilder();

    public T addValue( String name, String value ){
        bldr.append( name ).append( '=' ).append( value ).append(',');
        @SuppressWarnings("unchecked")
        T result = (T)this;
        return result;
    }

    public String toString(){
        return bldr.toString();
    }
}

private static class IntBuilder extends AbstractBuilder<IntBuilder> {

    public IntBuilder addValue( String name, int value ){
        bldr.append( name ).append( '=' ).append( value ).append(',');
        return this;
    }
}

Note I also fixed the bug with the two private StringBuilder variables, which is almost certainly not what you want.

于 2012-12-14T21:48:21.123 に答える