How to use builder pattern and inheritance with same attributes
Asked Answered
D

1

5

In fact, I would like to ask if my approach is correct, as maybe I should not use builder pattern right here.

I currently have the following class CsvItem:

public class CsvItem {
    private CsvItemGroup group;
    private CsvItemEntity entity;
    private String att1;
    private String att2;
    private String att3;
    private String att4;
    private String att5;
    private String att6;
    private String att7;
    private String att8;

    CsvItem(
            CsvItemGroup group,
            CsvItemEntity entity,
            String att1,
            String att2,
            String att3,
            String att4,
            String att5,
            String att6,
            String att7,
            String att8) {

        this.group = group;
        this.entity = entity;
        this.att1 = att1;
        this.att2 = att2;
        this.att3 = att3;
        this.att4 = att4;
        this.att5 = att5;
        this.att6 = att6;
        this.att7 = att7;
        this.att8 = att8;
    }
}

And I have some subclasses which extends from CsvItem like, CsvItemA:

public class CsvItemADW extends CsvItem {

    public CsvItemADW(CsvItemEntity entity,
                  String att1,
                  String att2,
                  String att3,
                  String att4,
                  String att5,
                  String att6,
                  String att7,
                  String att8) {

        super(CsvItemGroup.A, entity, att1, att2, att3, att4, att5, att6, att7, att8);
    }
}

This approach actually works, and if I have another class like CsvItemB I only have to modify the constructor in order to send CsvItemGroup.B.

The matter here is that I wanted to use builder pattern in superclass in order to use only the attributes that I need and void creating a constructor with empty or null values.

The problem that I'm facing is that I don't want to repeat code, and If I use the builder pattern in the subclasses I will have a lot of duplicated code. Note that superclass and subclasses have the same attributes, the only thing that changes is the itemGroup.

Example of builder pattern usage:

public class CsvItem {

private final CsvItemGroup group;
private final CsvItemEntity entity;
private final String att1;
private final String att2;
private final String att3;
private final String att4;
private final String att5;
private final String att6;
private final String att7;
private final String att8;

private CsvItem(CsvItemBuilder csvItemBuilder) {
    this.group = csvItemBuilder.group;
    this.entity = csvItemBuilder.entity;
    this.att1 = csvItemBuilder.att1;
    this.att2 = csvItemBuilder.att2;
    this.att3 = csvItemBuilder.att3;
    this.att4 = csvItemBuilder.att4;
    this.att5 = csvItemBuilder.att5;
    this.att6 = csvItemBuilder.att6;
    this.att7 = csvItemBuilder.att7;
    this.att8 = csvItemBuilder.att8;
}

public static class CsvItemBuilder{
    private final CsvItemGroup group;
    private final CsvItemEntity entity;
    private String att1;
    private String att2;
    private String att3;
    private String att4;
    private String att5;
    private String att6;
    private String att7;
    private String att8;

    public CsvItemBuilder(CsvItemGroup itemGroup, CsvItemEntity itemEntity) {
        this.group = itemGroup;
        this.entity = itemEntity;
    }

    public CsvItemBuilder withAtt1(String att1) {
        this.att1 = att1;
        return this;
    }

    public CsvItemBuilder withAtt2(String att2) {
        this.att2 = att2;
        return this;
    }

    // ... same with all attX

    public CsvItem build() {
        return new CsvItem(this);
    }
}
}
Don answered 4/6, 2018 at 15:51 Comment(2)
Before everything, I suggest you to use Arrays instead of like attr1, attr2 etc.Feer
I wanted to recreate that problem, I don't have all those attributes. But thank you! (I used all attributes as Strings, but this is not real, there are some attributes that are not string, etc)Don
A
13

This sound like a Builder pattern for class hierarchies problem (Effective Java). Your generic CsvItem would be like Pizza from the Book's example:

public abstract class Pizza {
    final Set toppings;

    Pizza(Builder<?> builder) {
        toppings = builder.toppings.clone();
    }

    public enum Topping {HAM, MUSHROOM, ONION, PEPPER, SAUSAGE}

    abstract static class Builder<T extends Builder> {
        EnumSet toppings = EnumSet.noneOf(Topping.class);
        abstract Pizza build();

        public T addTopping(Topping topping) {
            toppings.add(Objects.requireNonNull(topping));
            return self();
        }

        // Subclasses must override this method to return "this"
        protected abstract T self();
    }
}

And then, your specific CsvItem would be like NyPizza and Calzone:

public class NyPizza extends Pizza {
    private final Size size;

    private NyPizza(Builder builder) {
        super(builder);
        size = builder.size;
    }

    public enum Size {SMALL, MEDIUM, LARGE}


    public static class Builder extends Pizza.Builder<Builder> {
        private final Size size;

        public Builder(Size size) {
            this.size = Objects.requireNonNull(size);
        }

        @Override
        public NyPizza build() {
            return new NyPizza(this);
        }

        @Override
        protected Builder self() {
            return this;
        }
    }
}

public class Calzone extends Pizza {
    private final boolean sauceInside;

    private Calzone(Builder builder) {
        super(builder);
        sauceInside = builder.sauceInside;
    }

    public static class Builder extends Pizza.Builder<Builder> {
        private boolean sauceInside = false; // Default

        public Builder sauceInside() {
            sauceInside = true;
            return this;
        }

        @Override
        public Calzone build() {
            return new Calzone(this);
        }

        @Override
        protected Builder self() {
            return this;
        }
    }
}

And to use it:

NyPizza pizza = new NyPizza.Builder(SMALL)
 .addTopping(SAUSAGE).addTopping(ONION).build();
Calzone calzone = new Calzone.Builder()
 .addTopping(HAM).sauceInside().build();

Hope it helps you.

Allbee answered 6/6, 2018 at 8:4 Comment(4)
That's exactly what I needed!Don
Hello! Your answer helped me very much. However, I wondered if there is a way to add another layer, say Stuff ----->Food-Pizza-NyPizza-Calzone vs Drinks-Alcohol-Vodka-Wine. I tried to do it myself, but got caught up with all the generics and extensions of builders. If you could update your answer with expanded example (if it's at all possible), I would be very grateful!Tamarau
I ended up having to do conversion like return (T) self(); inside Alcohol's builder class method: public T setAlcoholVolume(double volume) {. It feels like a hack so your input would be very appreciated.Tamarau
The problem of this is that the constructor of the children class private NyPizza(Builder builder)is referencing Builder which could be the Builder of the parent or the Builder of the child. If it's the Builder of the Parent then the signature of builder won't match and if it's the Builder of the child then super won't workDavenport

© 2022 - 2024 — McMap. All rights reserved.