Builder and Prototype Patterns
The Builder Pattern
Problem: The "Telescoping Constructor"
When an object has many parameters (especially optional ones), constructors become unreadable and error-prone.
// Nightmarish constructor
new Request("GET", "google.com", 80, true, "timeout=500", "retry=3", null, ...);
Solution: Fluent API
The Builder pattern allows for step-by-step construction of complex objects using Chainable Methods. It separates the construction of an object from its representation.
Request req = Request.builder()
.method("POST")
.url("api.com")
.timeout(500)
.secure(true)
.build();
Key Advantages
- Immutability: You can build a complex object and make it
finalonce complete. - Safety: Unlike the "JavaBeans" pattern (setter methods), the object is not exposed in an inconsistent, partially-initialized state.
- Internal Logic: The
build()method can perform validation before the object is actually created.
The Prototype Pattern
Concept: Cloning over Newing
When creating an object is expensive (e.g., requires a DB query, heavy computation, or expensive I/O), you create a new instance by cloning an existing one.
public class Config implements Cloneable {
private List<String> data;
@Override
public Config clone() {
Config copy = (Config) super.clone();
copy.data = new ArrayList<>(this.data); // Crucial: Deep Copy
return copy;
}
}
Shallow Copy vs. Deep Copy
| Type | Primitive Fields | Reference Fields | Potential Risk |
|---|---|---|---|
| Shallow | Copied by value | Copied by reference | Modifying the copy changes the original |
| Deep | Copied by value | New instances created recursively | High memory/CPU overhead |
Deep Technical Insights
Lombok and Modern Builders
In real-world Java development, we rarely write the inner public static class Builder manually. We use the @Builder annotation from Lombok. It automatically generates all the boilerplate code at compile-time, keeping the source file pristine and readable.
Cloneable is Broken?
Java’s built-in Cloneable interface is widely considered a failure by architects. It doesn't actually have a clone() method (it’s a marker interface), and it requires handling CloneNotSupportedException. A better modern approach is a Copy Constructor or a Copy Factory:
// Better than Prototype/Cloneable
public User(User other) {
this.name = other.name;
this.tags = new ArrayList<>(other.tags);
}
Using this pattern is safer, doesn't require casting, and naturally supports final fields.