Питання 26 · Розділ 13

Як реалізувати Builder pattern для незмінного класу?

Builder виправданий при 4+ полях. Для 1-2 полів використовуйте конструктор — Builder додає зайвий клас і ускладнює код.

Мовні версії: English Russian Ukrainian

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

  1. Валідація — об’єкт не публікується, доки повністю не зібраний і не перевірений
  2. Атомарність — об’єкт або існує в повному стані, або не існує взагалі
  3. API Design — білдер самоописується, додавання поля не ламає виклики
  4. 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. Як правильно працювати з колекціями в незмінних класах]]