Search code examples
javadesign-patternsenumsbuilderdelegation

Step builder pattern using delegation and enums?


I have this project I'm working on and basically this is what I would like to achieve.

This is what I have:

MyObject obj = MyObject.builder()
                       .withValue("string")
                       .withAnotherValue("string")
                       .build();

MyObject obj = MyObject.builder()
                       .withValue("string")
                       .withAnotherValue("string")
                       .withField("key", "value")
                       .build();

So the step builder pattern forces the user to use the withValue() method and the withAnotherValue() method in that order. The method field() is optional and can be used as many times as you want.I followed this website for example http://www.svlada.com/step-builder-pattern/

So what I would like to achieve is this:

MyObject obj = MyObject.builder(Type.ROCK)
                       .withColour("blue")
                       .withValue("string")
                       .withAnotherValue("string")
                       .build();

MyObject obj = MyObject.builder(Type.STONE)
                       .withWeight("heavy")
                       .withValue("string")
                       .withAnotherValue("string")
                       .withField("key", "value")
                       .build();

So in the builder() method you'd put an enum type and based on the enum you'd have a different set of methods appear. So for ROCK the withValue(),withAnotherValue() and withColour() are now mandatory. But for STONE withWeight(), withAnotherValue() and withColour() are mandatory.

I something like this possible? I have been trying for the past two days to figure this out but I just can't seem to get it to give specific methods for each type. It just shows all the methods in the Builder.

Any thoughts and help is much appreciated.

Code:

Enum

public enum Type implements ParameterType<Type> {

  ROCK, STONE

}

ParameterType

interface ParameterType<T> {}

MyObject

public class MyObject implements Serializable {

  private static final long serialVersionUID = -4970453769180420689L;

  private List<Field> fields = new ArrayList<>();

  private MyObject() {
  }

  public interface Type {

    Value withValue(String value);
  }

  public interface Value {

    Build withAnotherValue(String anotherValue);
  }

  public interface Build {

    MyObject build();
  }

  public Type builder(Parameter type) {
    return new Builder();
  }

  public static class Builder implements Build, Type, Value {

    private final List<Field> fields = new ArrayList<>();

    @Override
    public Build withAnotherValue(String anotherValue) {
      fields.add(new Field("AnotherValue", anotherValue));
      return this;
    }

    @Override
    public Value withValue(String value) {
      fields.add(new Field("Value", value));
      return this;
    }

    @Override
    public MyObject build() {
      MyObject myObject = new MyObject();
      myObject.fields.addAll(this.fields);
      return myObject;
    }
  }

}

Solution

  • Considering you are looking for specific methods for a specific type of builder, having multiple builders, one for each type of MyObject that can be built may work best. You can create an interface that defines the builder and then put the common functionality into an abstract class, from which the individual builders extend. For example:

    public interface Builder {
        public MyObject build();
    }
    
    public abstract class AbstractBuilder() {
    
        private final List<Field> fields = new ArrayList<>();
    
        protected void addField(String key, String value) {
            fields.add(new Field(key, value));
        }
    
        @Override
        public MyObject build() {
            MyObject myObject = new MyObject();
            myObject.fields.addAll(this.fields);
            return myObject;
        }
    }
    
    public class StoneBuilder extends AbstractBuilder {
    
        public StoneBuilder withValue(String value) {
            addField("Value", value);
            return this;
        }
    
        // ...More builder methods...
    }
    
    public class RockBuilder extends AbstractBuilder {
    
        public RockBuilder withAnotherValue(String value) {
            addField("AnotherValue", value);
            return this;
        }
    
        // ...More builder methods...
    }
    

    This allows you to build MyObject instances in the following manner:

    MyObject obj = new RockBuilder()
                       .withValue("string")
                       .build();
    
    MyObject obj = new StoneBuilder()
                       .withAnotherValue("string")
                       .build();