I have a hierarchy of interfaces, with Child
implementing Parent
. I would like to work with immutable objects, so I would like to design Builder
classes that construct these objects conveniently. However, I have many Child
interfaces, and I don't want to repeat the code for building Parent
s in each type of child builder.
So, assume the following definitions:
public interface Parent { public Long getParentProperty(); } public interface Child1 extends Parent { public Integer getChild1Property(); } public interface Child2 extends Parent { public String getChild2PropertyA(); public Object getChild2PropertyB(); }
How can I efficiently implement builders Child1Builder
and Child2Builder
? They should support operation like:
Child1 child1 = Child1Builder.newChild1().withChild1Property(5).withParentProperty(10L);
and
Child2 child2 = Child2Builder.newChild2().withChild2PropertyA("Hello").withParentProperty(10L).withChild2PropertyB(new Object());
I don't want to implement a special case of withParentProperty
for each child builder.
Edited to add second property to Child2
to clarify that this cannot be done with simple generics. I am not looking for a way to combine Child1
and Child2
- I am looking for a way to implement a Builder
system that does not duplicate the work of building the parent class for every child class.
Thanks for any help!
Builder pattern aims to “Separate the construction of a complex object from its representation so that the same construction process can create different representations.” It is used to construct a complex object step by step and the final step will return the object.
If you look at just the intent of Builder pattern, it's easy to see that "StringBuilder is a Builder".
Advantages of the Builder pattern include: Allows you to vary a product's internal representation. Encapsulates code for construction and representation. Provides control over steps of construction process.
Builder Pattern: When to Use This pattern should be used: When it's necessary to use a constructor with a long parameter list or when there's a long list of constructors with different parameters. When it's necessary to build different representations of the same object.
The solution I imagine is like the Curiously Recurring Template Pattern, or CRTP. You can define a base class to handle the parent-related initialization, but you still may find the two boilerplate getParent()
and getThis()
methods to be too much repetition in each derived child-related builder class.
Take a look:
abstract class ParentBase implements Parent { @Override public final Long getParentProperty() { return parentProperty_; } protected void setParentProperty(Long value) { parentProperty_ = value; } private Long parentProperty_; } abstract class ParentBuilder<T extends ParentBuilder<T>> { T withParentProperty(Long value) { getParent().setParentProperty(value); return getThis(); } protected abstract ParentBase getParent(); protected abstract T getThis(); } final class ConcreteChild1 extends ParentBase implements Child1 { @Override public Integer getChild1Property() { return childProperty_; } public void setChild1Property(Integer value) { childProperty_ = value; } private Integer childProperty_; } final class Child1Builder extends ParentBuilder<Child1Builder> { public Child1Builder() { pending_ = new ConcreteChild1(); } public Child1Builder withChild1Property(Integer value) { pending_.setChild1Property(value); return this; } @Override protected ParentBase getParent() { return pending_; } @Override protected Child1Builder getThis() { return this; } private final ConcreteChild1 pending_; }
As you can see, the ParentBuilder
type expects to be cooperating with a derived type to allow it to return a properly-typed instance. Its own this
reference won't due, because the type of this
within ParentBuilder
is, of course, ParentBuilder
, and not, say, Child1Builder
as intended to maintain the "fluent" call chaining.
I owe the "getThis()
trick" to Angelika Langer's tutorial entry.
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