RabbitBones22
RabbitBones22

Reputation: 311

Subclassing builder pattern with a twist in Java

I would like to create an abstract Builder for an abstract class (although it does not need to be abstract) and every subclass of the abstract class could have its own subclass Builder. I also want every field/attribute to be filled AKA mandatory. So I use the Builder Patter With a Twist (https://blog.jayway.com/2012/02/07/builder-pattern-with-a-twist/).

I encountered a problem which was solved in this question I asked earlier: Generic parent object cannot be returned as child without cast But now I am unable to make multiple concrete/subclass Builders.

In the end I would like to instantiate objects something like this:

ConcreteBuilderA.getBuilder().setValue(Object value).setConcreteValue(int num).build()

Where setValue() belongs to the AbstractBuilder and the others to the concreteBuilder.

My best shot was (heavily simplified and abstracted):

/**
* @param<B> the type of builded object it should return.
* @param<S> the type of the builder subclass.
* @param<L> the type of the linking interface.
*/
public abstract class AbstractBuilder<B extends AbstractClass, S extends AbstractBuilder, L> implements ValueSetter<L>
{
    protected B buildable;
    @Override
    public L setValue(Object value)
    {
         //set the value
         return this;//<-- returns Object, blocking the use of the ConcreteBuilder methods
    }
    public abstract B build();
}

|

public class ConcreteBuilder extends AbstractBuilder<ConcreteProduct, ConcreteBuilder, ConcreteValueSetter> implements ConcreteValueSetter
{
    @Override
    public ConcreteBuilder setConcreteValue(int num)
    {
        //set value
        return this;
    }
    @Override
    public ConcreteProduct build();
    {
        return buildable;
    }
}

|

public interface ValueSetter<L>
{
     public L setValue(Object value);
}

|

public interface ConcreteValueSetter
{
    public ConcreteBuilder setConcreteValue(int num);
}

As marked, this stops the chain when 'switching' to the subclass build methods. I have made some variants to this and I cannot get it to work.

So I am really wondering if this is even possible. If it is, I would like to see how. If it isn't I would like know some technique that does meet my requirements.

Thanks in advance!

Upvotes: 3

Views: 861

Answers (2)

DwB
DwB

Reputation: 38290

Your problem is a symptom of lazy programming technique. Try this; pretend that you are a professional software developer:

ConcreteBuilderA builder = ConcreteBuilderA.getBuilder();
ThingBeingBuilt thing;

builder.setValue(value);
builder.setConcreteValue(num);
thing = builder.build()

Upvotes: 1

RabbitBones22
RabbitBones22

Reputation: 311

I discovered the answer with the with the help of Federico Peralta Schaffner. It is likely that I made the builder in my real project to complicated. So here is the code for a Builder-with-a-twist + inheritance:

/**
 *
 * @param <P> the type of the product.
 * @param <L> the linking interface.
 */
public class AbstractBuilder<P extends AbstractClass, L> implements ValueSetterOne<L>, ValueSetterTwo<L>{

    protected P toBeBuild;
    @Override
    public L setValueTwo(int value) {
        //set value
        return (L) this;
    }
    @Override
    public ValueSetterTwo<L> setValueOne(int value){
        //set value
        return this;
}

|

public class ConcreteBuilder extends AbstractBuilder<ConcreteClass, NameSetter> implements NameSetter, Optional{
    public static ValueSetter<NameSetter> getBuilder()
    {
        AbstractBuilder<ConcreteClass, NameSetter> builder = new ConcreteBuilder();
        builder.toBeBuild = new ConcreteClass();
        return builder;
    }

    @Override
    public Optional buildName(String name) {
        this.toBeBuild.setCharacterName(name);
        return this;
    }

    @Override
    public ConcreteClass build() {
        return this.toBeBuild;
    }

    @Override
    public Optional addExtraObject(Object extra) {
        System.out.println("test");
        return this;
    }
}

|

public interface ValueSetterOne<L> {
    public ValueSetterTwo<L> setValueOne(int value);
}

|

public interface ValueSetterTwo<L> {
    public L setValue(int value);
}

|

public interface NameSetter {
    public Optional buildName(String name);
}

|

public interface Optional {
    public ConcreteClass build();
    public Optional addExtraObject(Object extra);
}

And then to test it: ConcreteBuilder.getBuilder().setValueOne(0).setValueTwo(1).buildName("tricky").addExtraObject(args).build();

Upvotes: 1

Related Questions