1

I implemented pattern based on this answer I have the following asbtract config:

public abstract class AbstractConfig {

    public static abstract class Builder<B extends Builder<B>> {

        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<B extends DialogConfigBuilder<B>> extends Builder<B> {

        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.

6
  • 2
    You're using a raw type which means B resolves to Builder. Change your class declaration to class DialogConfigBuilder extends Builder<DialogConfigBuilder>. Commented Aug 15, 2017 at 6:48
  • Note that you should be wary of copying that solution as it also uses raw types. Commented Aug 15, 2017 at 6:54
  • @shmosel Than you for your comment. But if I change to class DialogConfigBuilder extends Builder<DialogConfigBuilder> can I extend DialogConfig in SuperDialogConfig? I think that if I follow your advise then setCalories() will return DialogConfig but not SuperDialogConfig. Commented Aug 15, 2017 at 7:10
  • 1
    Hmm... what if you do new DialogConfig.DialogConfigBuilder<>(), does that compile? Do you need to be to instantiate both builder classes, or just the subclass? Btw, SuperDialogConfig is a Super confusing name for a subclass. Commented Aug 15, 2017 at 7:22
  • @shmosel I changed SuperDialogConfig to ExtendedDialogConfig. Answering to your question - I need DialogConfig, ExtendedDialogConfig and other sub classes where each implements builder pattern. Commented Aug 15, 2017 at 7:27

2 Answers 2

1

You would first change setCalories() to:

public Builder<B> 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 extends B> 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<B extends Builder<B>> {
        private int calories = 0;

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

        public abstract AbstractConfig build();
    }

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

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

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

        private double width;    
        private double height;

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

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

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

    private final double width;
    private final double height;

    protected <B extends DialogConfigBuilder<B>> DialogConfig(final DialogConfigBuilder<B> 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);
    }
}
Sign up to request clarification or add additional context in comments.

4 Comments

In your solution you make DialogConfig final and besides all setters in DialogConfigBuilder return DialogConfigBuilder. This is not what I need. I did write that I need to extend DialogConfig in other subclasses and each subclass must implement builder pattern.
@Pavel_K That is not my code. That part I copied from your input. And again - it is just an attempt to "complete" the input for the given task. Nothing changes when you remove that final from that class. And the changed return type is about getting rid of casts and warnings!
I need some time to check
Sure ... thats up to you.
1

I found my mistake. This is how I used DialogConfigBuilder

DialogConfig config = new DialogConfig.DialogConfigBuilder()
                .setWidth(0)
                .setCalories(0)
                .setHeight(0) //X LINE
                .build();

This is how I should use DialogConfigBuilder

DialogConfig config = new DialogConfig.DialogConfigBuilder<>()
                .setWidth(0)
                .setCalories(0)
                .setHeight(0) //X LINE
                .build();

Pay attention to <> in the second case.

1 Comment

You should accept your self-answer then. And maybe put a thick first line in there "my mistake was to use raw types and now I understand why everybody says do not use raw types" ;-)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.