Search code examples
javadesign-patternsbuilder-pattern

How to use builder pattern and inheritance with same attributes


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);
    }
}
}

Solution

  • 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.