Як реалізувати 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 без @Singular безпечний» — без @Singular колекції не копіюються
- «Builder завжди потрібен» — для 1-2 полів конструктор простіший
Пов’язані теми:
- [[3. Як створити незмінний клас в Java]]
- [[20. Що таке Record і як він допомагає створювати незмінні класи]]
- [[25. Чи є недоліки у незмінних об’єктів]]
- [[29. Як правильно працювати з колекціями в незмінних класах]]