user3208218
user3208218

Reputation: 75

JAVA Returning the parent object - Inheritance from inheritance - Builder Pattern

I'm sure this issue might have already been answered but after looking around, I'm not sure of the correct terminology to get me a solid answer. Either this, or I'm not fully understanding something.

I'm trying to create a selection of builders that have varying methods, however, they must all inherit from the "base" builder. This is fine, but I can't get it to return the right object to continue the builder pattern.

An example of what I've tried:

public class Builder1 {
    protected String string = new String();

    public <T extends Builder1 > T append1(String string) {
        this.string += string;
        return (T)this;
    }

    public void print() {
        System.out.println(this.string);
    }

}


public class Builder2 extends Builder1 {

    public <T extends builder2 > T append2(String string) {
        this.string += string;
        return (T)this;
    }

}


public class Builder3 extends Builder2 {

    public <T extends Builder3 > T append3(String string) {
        this.string += string;
        return (T)this;
    }

}

So, if I do this:

 new Builder3().append3("")...

I can access all methods in Builder3, Builder2 and Builder1 - Great.
The issue occurs when I access one of the methods in Builder1 or Builder2, like so:

 new Builder3().append1("")...

Now, I can only access the methods of Builder1, and I can't get to Builder2 or Builder3.

As I said, I'm sure this has already been answered somewhere else so feel free to point me to any posts about it.
Any help on this would be greatly appreciated, thanks.

EDIT:
I should also point out that the methods will all be doing different things. My example makes it seem like they are doing the same thing, from different places.

Upvotes: 2

Views: 510

Answers (4)

caisil
caisil

Reputation: 373

According to the Type Erasure definition

Replace all type parameters in generic types with their bounds or Object if the type parameters are unbounded.

So, after append1(), the return type actually is Builder1. In order to append2 or append3 after append1, you can change <T extends Builder*> to <T extends Builder3> in all the 3 classes.

Upvotes: 1

Vince
Vince

Reputation: 15146

To start off, you should be using a StringBuilder for appending text, rather than String string = new String().

string += string creates a new StringBuilder, which means you're creating one every time your Builder#append methods are called.

class Builder1 {
    private StringBuilder builder = new StringBuilder();

    public Builder1 append1(String text) {
        builder.append(text);
        return this;
    }

    public String build() {
        return builder.toString();
    }
}

Next, you should know the BaseBuilder interface should only expose behaviors that all builders have. You should not be defining append2 or append3 to this interface. It should only contain build:

interface Builder<T> {
    T build();
}

Finally, for the solution, you should be decorating to add the functionality.

You would have Builder1:

class Builder1 implements Builder<String> {
    private StringBuilder builder = new StringBuilder();

    public Builder1 append1(String text) {
        builder.append(text);
        return this;
    }

    @Override
    public String build() {
         return string.toString();
    }
}

Builder1 itself expresses how you should continue the decorating for the other builders:

  • Builder1 is composed of it's own builder.
  • Calls are delegated to that builder, but the current instances is returned.

Builder2 would be composed of a Builder1:

class Builder2 implements Builder<String> {
    private Builder1 builder = new Builder1();

    public Builder2 append(String text) {
        builder.append(text);
        return this;
    }

    public Builder2 append2(String text) {
        //custom behavior
        return this;
    }

    public String build() {
        return builder1.build();
    }
}

Builder3 would be composed of Builder2:

class Builder3 implements Builder<String> {
    private Builder2 builder = new Builder2();

    public Builder3 append1(String text) {
        builder.append1(text);
        return this;
    }

    public Builder3 append2(String text) {
        builder.append2(text);
        return this;
    }

    // custom append3

    // build() returns builder.build()
}

Interfaces exist for interacting: Builder defines how all builders can be interacted with. If you don't want Builder1 to have append3, then the base interface should not define it.

Upvotes: 1

Debosmit Ray
Debosmit Ray

Reputation: 5403

[Disclaimer: untested code] I would do an abstract base class that defined the base append* methods. That ABC would look something like...

public abstract class BaseBuilder {

    protected String string = "";

    public void print() {
        System.out.println(this.string);
    }

    abstract <T extends BaseBuilder> T append1(String string);

    abstract <T extends BaseBuilder> T append2(String string);

    abstract <T extends BaseBuilder> T append3(String string);

}

Then, say, for Builder1, you could implement append1(..), and throw exceptions for the others.

public class Builder1 extends BaseBuilder {
    public <T extends Builder1> T append1(String string) {
        this.string += string;
        return (T)this;
    }

    public <T extends Builder1> T append2(String string) {
        throw new UnsupportedOperationException("something");
    }

    public <T extends Builder1> T append3(String string) {
        throw new UnsupportedOperationException("something");
    }
}

Same principle for Builder2, Builder3, and so on

Upvotes: 2

Steve11235
Steve11235

Reputation: 2923

LOL! I just went through this. What a pain. This works for me. It's located in the base class. However, I pass T back to the base class. You can get the same effect by overriding the method in every child class, with each class returning its own type.

/**
 * Return this as type T. This is used by fluent setters and build.
 * 
 * @return
 */
@SuppressWarnings("unchecked")
protected T fetchThisAsT() {
    return (T) this;
}

Upvotes: 0

Related Questions