Reputation: 75
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
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
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.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
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
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