Зачем нужен паттерн Builder?

«Зачем нужен паттерн Builder?» — вопрос из категории Паттерны, который задают на 24% собеседований PHP Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Паттерн Builder я применяю, когда сталкиваюсь с необходимостью создания сложных объектов, имеющих много конфигурируемых параметров, особенно если часть из них необязательна или имеет значения по умолчанию. Он инкапсулирует логику конструирования и позволяет создавать объекты пошагово, делая код клиента чище и читабельнее.

Проблема, которую он решает:

  • Телескопический конструктор: Конструктор с десятком параметров, где большинство — null или значения по умолчанию, запутан и подвержен ошибкам.
  • Неизменяемость (immutability): Если объект должен быть immutable после создания, нельзя использовать сеттеры. Builder позволяет сконфигурировать все поля перед финальным созданием.

Преимущества в использовании:

  • Читаемость: Цепочка методов (builder.setName("X").setPower(100).build()) явно показывает, что конфигурируется.
  • Гибкость: Можно создавать разные вариации объекта, используя один и тот же процесс построения.
  • Изоляция сложности: Логика валидации параметров и сборки объекта скрыта внутри Builder'а.

Пример реализации на Java для конфигурации HTTP-клиента:

public class HttpClient {
    private final String baseUrl;
    private final int timeout;
    private final boolean useCache;
    // Приватный конструктор, вызывается только Builder'ом
    private HttpClient(Builder builder) {
        this.baseUrl = builder.baseUrl;
        this.timeout = builder.timeout;
        this.useCache = builder.useCache;
    }

    public static class Builder {
        private String baseUrl = "http://localhost"; // Значение по умолчанию
        private int timeout = 5000;
        private boolean useCache = false;

        public Builder baseUrl(String url) { this.baseUrl = url; return this; }
        public Builder timeout(int ms) { this.timeout = ms; return this; }
        public Builder useCache(boolean flag) { this.useCache = flag; return this; }

        public HttpClient build() {
            // Можно добавить валидацию параметров здесь
            if (timeout <= 0) throw new IllegalArgumentException("Timeout must be positive");
            return new HttpClient(this);
        }
    }
}

// Использование
HttpClient client = new HttpClient.Builder()
        .baseUrl("https://api.example.com")
        .timeout(10000)
        .useCache(true)
        .build();