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);
    }
}
}
  • 1
    Before everything, I suggest you to use Arrays instead of like `attr1`, `attr2` etc. – Pradeep Simha Jun 04 '18 at 15:54
  • 1
    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) – Mohamed Said Benmousa Jun 04 '18 at 15:57

1 Answers1

12

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.

Josemy
  • 810
  • 1
  • 12
  • 29
  • That's exactly what I needed! – Mohamed Said Benmousa Jun 06 '18 at 11:34
  • 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! – parsecer Nov 12 '19 at 00:51
  • 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. – parsecer Nov 12 '19 at 01:17
  • 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 work – Ghilteras Feb 18 '20 at 00:02