Question
How can I implement the Builder Pattern in Java while dealing with deep inheritance trees without encountering compilation issues?
public class Rabbit {
public String sex;
public String name;
public Rabbit(Builder builder) {
sex = builder.sex;
name = builder.name;
}
public static class Builder {
protected String sex;
protected String name;
public Builder() { }
public Builder sex(String sex) {
this.sex = sex;
return this;
}
public Builder name(String name) {
this.name = name;
return this;
}
public Rabbit build() {
return new Rabbit(this);
}
}
}
Answer
The Builder Pattern provides a flexible solution for constructing complex objects while avoiding the limitations of telescoping constructors and ensuring proper compatibility with inheritance in Java. This article explores a practical implementation for using the Builder Pattern in deep inheritance hierarchies.
public class Rabbit {
public String sex;
public String name;
public Rabbit(Builder builder) {
sex = builder.sex;
name = builder.name;
}
public static <T extends Builder<T>> class Builder {
protected String sex;
protected String name;
public Builder() { }
public T sex(String sex) {
this.sex = sex;
return (T) this;
}
public T name(String name) {
this.name = name;
return (T) this;
}
public Rabbit build() {
return new Rabbit(this);
}
}
}
public class Lop extends Rabbit {
public float earLength;
public String furColour;
public Lop(LopBuilder builder) {
super(builder);
this.earLength = builder.earLength;
this.furColour = builder.furColour;
}
public static class LopBuilder extends Rabbit.Builder<LopBuilder> {
protected float earLength;
protected String furColour;
public LopBuilder() { }
public LopBuilder earLength(float length) {
this.earLength = length;
return this;
}
public LopBuilder furColour(String colour) {
this.furColour = colour;
return this;
}
public Lop build() {
return new Lop(this);
}
}
}
Causes
- Deep inheritance hierarchies complicate constructor chaining due to method resolution issues in inheriting Builder classes.
- Telescoping constructors lead to impractical and unreadable code when managing many parameters across derived classes.
Solutions
- Implement a builder for each class, ensuring that the builder methods return the correct type for fluid chaining.
- Use generics to return the derived builder type, allowing for additional parameters in derived classes without needing to override methods explicitly.
Common Mistakes
Mistake: Not ensuring that builder methods in subclasses return the correct type for chaining.
Solution: Use generics in your builder class signature to specify the builder's return type.
Mistake: Overcomplicating the builder structure with unnecessary overrides that break chaining.
Solution: Leverage the Curiously Recurring Template Pattern for cleaner implementation.
Helpers
- Builder Pattern
- Java inheritance
- Builder pattern with inheritance
- Java design patterns
- Curiously Recurring Template Pattern
- Object construction in Java