Как реализовать Builder pattern для иммутабельного класса?
Builder оправдан при 4+ полях. Для 1-2 полей используйте конструктор — Builder добавляет лишний класс и усложняет код.
Junior Level
Builder — это паттерн, который помогает создавать объекты с большим количеством полей, особенно если есть опциональные поля.
Проблема: слишком много конструкторов
// "Telescoping constructors" — плохо
new User("Ivan");
new User("Ivan", 25);
new User("Ivan", 25, "ivan@mail.com");
new User("Ivan", 25, "ivan@mail.com", "admin");
Решение: Builder
User user = new User.Builder("Ivan")
.age(25)
.email("ivan@mail.com")
.role("admin")
.build();
Когда Builder избыточен
Builder оправдан при 4+ полях. Для 1-2 полей используйте конструктор — Builder добавляет лишний класс и усложняет код.
Middle Level
Классическая реализация
public final class UserProfile {
private final String username; // обязательный
private final String email; // опциональный
private final int age; // опциональный
private UserProfile(Builder builder) {
this.username = builder.username;
this.email = builder.email;
this.age = builder.age;
}
public String getUsername() { return username; }
public String getEmail() { return email; }
public int getAge() { return age; }
public static class Builder {
private final String username;
private String email;
private int age;
public Builder(String username) {
this.username = username; // обязательное поле
}
public Builder email(String email) {
this.email = email;
return this;
}
public Builder age(int age) {
this.age = age;
return this;
}
public UserProfile build() {
return new UserProfile(this);
}
}
}
Lombok (@Builder)
@Value // final class, private final поля, equals/hashCode/toString
@Builder // @Builder генерирует статический Builder класс с методами для каждого поля.
public class UserProfile {
String username;
String email;
int age;
}
toBuilder() — для “изменения” иммутабельного объекта
UserProfile updated = user.toBuilder().age(30).build();
Senior Level
Валидация в build()
Метод build() — идеальное место для cross-field validation:
public UserProfile build() {
if (email != null && !email.contains("@")) {
throw new IllegalArgumentException("Invalid email");
}
return new UserProfile(this);
}
Lombok @Singular для коллекций
@Value @Builder
public class Order {
String id;
@Singular List<String> items; // автоматическое защитное копирование
}
@Singular автоматически делает List.copyOf() в build().
Почему это важно для Senior
- Валидация — объект не публикуется, пока полностью не собран и не проверен
- Атомарность — объект либо существует в полном состоянии, либо не существует вовсе
- API Design — билдер самодокументируется, добавление поля не ломает вызовы
- Immutability контракт — билдер — единственное мутабельное место, основной класс полностью иммутабелен
Резюме для Senior
- Builder для объектов с 4+ полями
- Совмещайте Builder с
finalполями основного класса - Lombok
@Builder+@Singular— минимум бойлерплейта с защитным копированием - Всегда добавляйте
toBuilder()для удобства модификации build()— место для финальной валидации
🎯 Шпаргалка для интервью
Обязательно знать:
- Builder решает проблему “telescoping constructors” — удобные имена параметров через fluent API
- Классическая реализация: static inner Builder class с chaining,
build()создаёт immutable объект - Приватный конструктор иммутабельного класса принимает Builder — единственное мутабельное место
- Lombok
@Builder+@Value— минимум boilerplate;@Singularавтоматически делаетList.copyOf() toBuilder()— для удобного “изменения” иммутабельного объектаbuild()— место для cross-field validation
Частые уточняющие вопросы:
- Когда Builder оправдан? — 4+ полей, особенно с опциональными
- Что делает @Singular? — Автоматическое защитное копирование коллекций (
List.copyOf()) - Builder vs Record? — Record для простых DTO; Builder для сложных объектов с валидацией
- Зачем приватный конструктор? — Чтобы объект создавался только через Builder с валидацией
Красные флаги (НЕ говорить):
- «Builder делает класс мутабельным» — Builder мутабелен, основной класс — final с final полями
- «Валидация в конструкторе Record лучше» — Builder позволяет валидацию ДО создания объекта
- «@Builder без @Sufficient безопасен» — без @Singular коллекции не копируются
- «Builder всегда нужен» — для 1-2 полей конструктор проще
Связанные темы:
- [[3. Как создать иммутабельный класс в Java]]
- [[20. Что такое Record и как он помогает создавать иммутабельные классы]]
- [[25. Есть ли недостатки у иммутабельных объектов]]
- [[29. Как правильно работать с коллекциями в иммутабельных классах]]