Builder pattern with inheritance support generics issue

Home / Uncategorized / Builder pattern with inheritance support generics issue

Question:
I implemented pattern based on this answer I have the following asbtract config:public abstract class AbstractConfig {

public static abstract class Builder> {

private int calories = 0;

public Builder() {

}

public B setCalories(int calories) {
this.calories = calories;
return (B) this;
}

public abstract AbstractConfig build();
}

private int calories = 0;

protected AbstractConfig(final Builder builder) {
calories = builder.calories;
}
}

And I have the following concrete config:public class DialogConfig extends AbstractConfig {

public static class DialogConfigBuilder> extends Builder {

private double width;

private double height;

public DialogConfigBuilder() {
//does nothing.
}

public B setWidth(final double value) {
width = value;
return (B) this;
}

public B setHeight(final double value) {
height = value;
return (B) this;
}
public DialogConfig build() {
return new DialogConfig(this);
}
}

private final double width;

private final double height;

protected DialogConfig(final DialogConfigBuilder builder) {
super(builder);
width = builder.width;
height = builder.height;
}

public double getWidth() {
return width;
}

public double getHeight() {
return height;
}
}

And this is how I use it
DialogConfig config = new DialogConfig.DialogConfigBuilder()
.setWidth(0)
.setCalories(0)
.setHeight(0) //X LINE
.build();

At X line I get – Can’t find symbol method setHeight. What is my mistake?

EDIT – I will have and a ExtendedDialogConfig that must extend DialogConfig and etc. I mean there will be other subclasses.


Answer:
You would first change setCalories() to:public Builder setCalories(int calories) {
this.calories = calories;
return this;
}

to get rid of that cast and the warning. And now look at this closely. You return a Builder. This code doesn’t know about future subclasses. It only returns an instance of that base builder.

As a consequence, when you have that chained call:.setHeight(0) .build();

that would return that base builder. To then call build() – which would build an abstract configuration. But you want to assign that to a more specific DialogConfig. Thus the error.

A (ugly) workaround:DialogConfig.DialogConfigBuilder builder = new DialogConfig.DialogConfigBuilder<>().setHeight(0);
builder.setCalories(0);

…config = builder.build();

And a solution – by again reworking setCalories():@SuppressWarnings("unchecked")
public T setCalories(int calories) {
this.calories = calories;
return (T) this;
}

Fixes the compile error; and allows chaining the setCalories() call as well. Final exercise of getting rid of the cast/suppress is left as exercise to the reader.

And for the record – the "complete" solution, including all adaptions to get rid of raw types and other warnings:abstract class AbstractConfig {
public static abstract class Builder> {
private int calories = 0;

@SuppressWarnings("unchecked")
public T setCalories(int calories) {
this.calories = calories;
return (T) this;
}

public abstract AbstractConfig build();
}

private int calories = 0;
public int getCalories() { return calories; }

protected > AbstractConfig(final Builder builder) {
calories = builder.calories;
}
}

final class DialogConfig extends AbstractConfig {
public static class DialogConfigBuilder> extends Builder {

private double width;
private double height;

public DialogConfigBuilder setWidth(final double value) {
width = value;
return this;
}

public DialogConfigBuilder setHeight(final double value) {
height = value;
return this;
}

public DialogConfig build() {
return new DialogConfig(this);
}
}

private final double width;
private final double height;

protected > DialogConfig(final DialogConfigBuilder builder) {
super(builder);
width = builder.width;
height = builder.height;
}

public double getWidth() { return width; }
public double getHeight() { return height; }
}

public class Builders {
public static void main(String[] args) {
DialogConfig config = new DialogConfig.DialogConfigBuilder<>().setHeight(0).setCalories(0).build();
System.out.println(config);
}
}
Read more

Leave a Reply

Your email address will not be published. Required fields are marked *