Вопрос 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 без @Sufficient безопасен» — без @Singular коллекции не копируются
  • «Builder всегда нужен» — для 1-2 полей конструктор проще

Связанные темы:

  • [[3. Как создать иммутабельный класс в Java]]
  • [[20. Что такое Record и как он помогает создавать иммутабельные классы]]
  • [[25. Есть ли недостатки у иммутабельных объектов]]
  • [[29. Как правильно работать с коллекциями в иммутабельных классах]]