Using a Generic Builder Pattern in Java to Simplify Object Creation
This article demonstrates how a universal Builder utility can replace verbose object construction in Java, allowing developers to instantiate a class and set multiple properties—including complex collections—in a single fluent chain, while also explaining the underlying implementation and usage examples.
Programmers often face the "do you have a girlfriend?" metaphor when dealing with objects that have many attributes, leading to cumbersome code with separate instantiation and setter calls.
The article first shows a traditional way of creating a GirlFriend object by calling new GirlFriend() and then invoking a series of setters for name, age, bust, waist, hips, hobby, birthday, address, mobile, email, hairColor, and gift, highlighting the maintenance difficulties.
To solve this, a generic Builder pattern is introduced that works for any class without requiring modifications to the original class or Lombok support. The Builder allows chaining of property assignments in a single statement.
public class Builder
{
private final Supplier
instantiator;
private List
> modifiers = new ArrayList<>();
public Builder(Supplier
instantiator) { this.instantiator = instantiator; }
public static
Builder
of(Supplier
instantiator) { return new Builder<>(instantiator); }
public
Builder
with(Consumer1
consumer, P1 p1) {
Consumer
c = (instance) -> consumer.accept(instance, p1);
modifiers.add(c);
return this;
}
public
Builder
with(Consumer2
consumer, P1 p1, P2 p2) {
Consumer
c = (instance) -> consumer.accept(instance, p1, p2);
modifiers.add(c);
return this;
}
public
Builder
with(Consumer3
consumer, P1 p1, P2 p2, P3 p3) {
Consumer
c = (instance) -> consumer.accept(instance, p1, p2, p3);
modifiers.add(c);
return this;
}
public T build() {
T value = instantiator.get();
modifiers.forEach(modifier -> modifier.accept(value));
modifiers.clear();
return value;
}
}
@FunctionalInterface
public interface Consumer1
{ void accept(T t, P1 p1); }
@FunctionalInterface
public interface Consumer2
{ void accept(T t, P1 p1, P2 p2); }
@FunctionalInterface
public interface Consumer3
{ void accept(T t, P1 p1, P2 p2, P3 p3); }Usage of the Builder is illustrated with a fluent chain that sets name, age, vital statistics, birthday, address, mobile, email, hair color, and multiple hobbies and gifts in one expression:
GirlFriend myGirlFriend = Builder.of(GirlFriend::new)
.with(GirlFriend::setName, "小美")
.with(GirlFriend::setAge, 18)
.with(GirlFriend::setVitalStatistics, 33, 23, 33)
.with(GirlFriend::setBirthday, "2001-10-26")
.with(GirlFriend::setAddress, "上海浦东")
.with(GirlFriend::setMobile, "18688888888")
.with(GirlFriend::setEmail, "[email protected]")
.with(GirlFriend::setHairColor, "浅棕色带点微卷")
.with(GirlFriend::addHobby, "逛街")
.with(GirlFriend::addHobby, "购物")
.with(GirlFriend::addHobby, "买东西")
.with(GirlFriend::addGift, "情人节礼物", "LBR 1912女王时代")
.with(GirlFriend::addGift, "生日礼物", "迪奥烈焰蓝金")
.with(GirlFriend::addGift, "纪念日礼物", "阿玛尼红管唇釉")
.build();The article concludes that this generic Builder supports up to three parameters per property method, is easy to extend, and dramatically reduces boilerplate when dealing with objects that have many fields.
Architect's Tech Stack
Java backend, microservices, distributed systems, containerized programming, and more.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.