Java
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
149: The Builder Pattern
I've seen this exact bug happen in production more times than I care to admit. You're working on a project with a complex data object—maybe a User profile or a Network Request—and you've got a constructor with six or seven parameters. Most of them are the same type, usually Strings.
// The "Telescoping Constructor" mess
public class EmailMessage {
private final String sender;
private final String recipient;
private final String subject;
private final String body;
private final String cc;
public EmailMessage(String sender, String recipient, String subject, String body, String cc) {
this.sender = sender;
this.recipient = recipient;
this.subject = subject;
this.body = body;
this.cc = cc;
}
}
// Somewhere in the service layer...
EmailMessage mail = new EmailMessage(
"customer@example.com", // Wait, is this the sender or recipient?
"support@company.com",
"Urgent Help",
"I can't log in!",
"manager@company.com"
);
The Silent Parameter Swap
The problem here isn't that the code is "broken" in a way that causes a crash. It's that it's broken in a way that the compiler loves. If you accidentally swap the sender and the recipient, Java doesn't care—they're both Strings. You'll ship the code, the tests might pass if you aren't checking the specific direction of the email, and then you'll realize a week later that your customers are receiving emails meant for your support team.
As you add more optional fields (like bcc, priority, or attachments), you either end up with a massive constructor that's impossible to read, or a "telescoping" nightmare where you have five different constructor overloads to handle every possible combination of optional parameters. It's a maintenance disaster.
Creating a Fluent Builder
The fix is the Builder Pattern. Instead of forcing the caller to remember the exact order of seven different Strings, we create a helper class that lets us set the values explicitly. I prefer the "Fluent" style because it reads like a sentence.
public class EmailMessage {
private final String sender;
private final String recipient;
private final String subject;
private final String body;
private final String cc;
// We make the constructor private so no one can bypass the builder
private EmailMessage(Builder builder) {
this.sender = builder.sender;
this.recipient = builder.recipient;
this.subject = builder.subject;
this.body = builder.body;
this.cc = builder.cc;
}
public static class Builder {
private String sender;
private String recipient;
private String subject;
private String body;
private String cc;
public Builder setSender(String sender) {
this.sender = sender;
return this; // This is the secret sauce for chaining
}
public Builder setRecipient(String recipient) {
this.recipient = recipient;
return this;
}
public Builder setSubject(String subject) {
this.subject = subject;
return this;
}
public Builder setBody(String body) {
this.body = body;
return this;
}
public Builder setCc(String cc) {
this.cc = cc;
return this;
}
public EmailMessage build() {
return new EmailMessage(this);
}
}
}
Why this solves the cognitive load
Now, look at how we instantiate the object. The ambiguity is gone. Even if you're a junior developer who has never seen the EmailMessage class before, you know exactly what is being assigned to what.
EmailMessage mail = new EmailMessage.Builder()
.setSender("support@company.com")
.setRecipient("customer@example.com")
.setSubject("Urgent Help")
.setBody("I can't log in!")
.build();
A few things to notice here that make this a professional implementation:
- Immutability: The fields in
EmailMessageremainfinal. We aren't using setters on the main object, which means once the email is built, it can't be accidentally changed. - Readability: The
return this;in the builder methods allows us to chain calls. I call this "fluent" because the code almost reads like a configuration file. - Validation: If you need to ensure that
senderandrecipientare never null, thebuild()method is the perfect place to put that logic. You can throw anIllegalStateExceptionright there before the object is even created.
📋 Practical Task
Exercise: Building a Custom PC Configuration System
You are building a system for a custom PC shop. A Computer object can have a variety of components, some of which are required and some of which are optional. Using the Builder Pattern, implement the following:
- Create a
Computerclass with the following final fields:cpu(String) - RequiredramSize(int) - Requiredgpu(String) - Optionalstorage(String) - OptionalisOverclocked(boolean) - Optional (defaults to false)
- Implement a static inner
Builderclass. - Ensure the
Computerconstructor is private. - Add a validation check inside the
build()method: ifcpuis null orramSizeis less than or equal to 0, throw anIllegalStateException("Invalid PC configuration"). - In a
mainmethod, instantiate oneComputerusing the builder with all fields set, and another with only the required fields.
There are no comments for now.