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;
}
}
}
This isn't possible using enum
, but you could do this with a custom enum
-like class:
public final class Type<B extends MyObject.Builder> {
private final Supplier<? extends B> supplier;
private Type(Supplier<? extends B> supplier) {
this.supplier = Objects.requireNonNull(supplier);
}
public B builder() {
return supplier.get();
}
public static final Type<MyObject.RockBuilder> ROCK =
new Type<>(MyObject.RockBuilder::new);
public static final Type<MyObject.StoneBuilder> STONE =
new Type<>(MyObject.StoneBuilder::new);
}
public class MyObject {
// ...
// And this method is probably superfluous at this point.
public static <B extends MyObject.Builder> builder(Type<? extends B> type) {
return type.builder();
}
}
You could adapt that approach to a step builder easily, but there's a separate issue here. Since each step in a step builder specifies the next step in the return type, you can't re-use step interfaces very easily. You would need to declare, for example, separate interfaces RockValueStep
, StoneValueStep
, etc. because the interfaces themselves specify the step order.
The only simple way around that would be if the separate types (rock, stone, etc.) only strictly added steps such that e.g. Type.ROCK
returns a ColourStep
and Type.STONE
returns a WeightStep
, and both ColourStep
and WeightStep
return ValueStep
:
// Rock builder starts here.
interface ColourStep { ValueStep withColour(String c); }
// Stone builder starts here.
interface WeightStep { ValueStep withWeight(String w); }
// Shared.
interface ValueStep { AnotherValueStep withValue(String v); }
And then:
public final class Type<B /* extends ABuilderStepMarker, possibly */> {
// (Constructor and stuff basically same as before.)
public static final Type<MyObject.ColourStep> ROCK =
new Type<>(/* implementation */::new);
public static final Type<MyObject.WeightStep> STONE =
new Type<>(/* implementation */::new);
}
The reasons this kind of thing can't be done using enum
are pretty much:
enum
can't be generic:
// This is an error.
enum Type<T> {
}
Although you could declare an abstract method on an enum
and override it with a covariant return type, the covariant return type is never visible:
// This is valid code, but the actual type of
// Type.ROCK is just Type, so the return type of
// Type.ROCK.builder() is just MyObject.Builder,
// despite the override.
enum Type {
ROCK {
@Override
public MyObject.RockBuilder builder() {
return new MyObject.RockBuilder();
}
};
public abstract MyObject.Builder builder();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With