Что такое паттерн проектирования Builder (Строитель) и в чём его преимущества?

Ответ

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

Решаемая проблема

Избегание "телескопического конструктора" (множества перегруженных версий с разными наборами параметров) и необходимости передавать null для неиспользуемых полей.

Классическая реализация на Java

public class Computer {
    // Обязательные параметры (final)
    private final String cpu;
    private final String ram;
    // Необязательные параметры
    private final String storage;
    private final String gpu;

    // Приватный конструктор, принимает Builder
    private Computer(ComputerBuilder builder) {
        this.cpu = builder.cpu;
        this.ram = builder.ram;
        this.storage = builder.storage;
        this.gpu = builder.gpu;
    }

    // Статический вложенный класс Builder
    public static class ComputerBuilder {
        // Обязательные параметры
        private final String cpu;
        private final String ram;
        // Необязательные параметры с значениями по умолчанию
        private String storage = "256GB SSD";
        private String gpu = "Integrated";

        // Конструктор Builder с обязательными параметрами
        public ComputerBuilder(String cpu, String ram) {
            this.cpu = cpu;
            this.ram = ram;
        }

        // Методы для установки необязательных параметров (возвращают this для fluent API)
        public ComputerBuilder storage(String storage) {
            this.storage = storage;
            return this;
        }

        public ComputerBuilder gpu(String gpu) {
            this.gpu = gpu;
            return this;
        }

        // Финальный метод build() создает целевой объект
        public Computer build() {
            // Здесь можно добавить валидацию параметров
            if (cpu == null || ram == null) {
                throw new IllegalArgumentException("CPU and RAM are required");
            }
            return new Computer(this);
        }
    }
}

// Использование (Fluent Interface):
Computer myPC = new Computer.ComputerBuilder("Intel i7", "16GB DDR4")
        .storage("1TB NVMe")
        .gpu("NVIDIA RTX 4070")
        .build(); // Создается неизменяемый объект Computer

Ключевые преимущества

  1. Читаемость и ясность: Имена методов (storage(), gpu()) делают код самодокументируемым.
  2. Гибкость: Позволяет создавать объекты с разными комбинациями параметров, не загромождая класс множеством конструкторов.
  3. Неизменяемость (Immutability): Целевой объект (Computer) может быть сделан неизменяемым (final поля), что повышает надежность в многопоточных средах.
  4. Валидация: Проверку корректности параметров можно централизованно выполнить в методе build() перед созданием объекта.

Альтернативы в современном Java

  • Lombok @Builder: Генерирует код Builder автоматически.
  • Record с Builder (в комбинации с библиотеками): Для создания неизменяемых DTO.

Ответ 18+ 🔞

Ну что за жизнь, блядь! Опять этот ваш паттерн Builder, как будто без него мир рухнет. Слушай сюда, я тебе на пальцах объясню, а то смотрю, глаза у тебя уже стеклянные, как у этой самой Муму перед тем, как её в озеро отправили.

Представь, что тебе нужно собрать комп, да? Ну, системный блок, эту железную коробку, которая гудит как пылесос. Так вот, вариантов сборки — овердохуища! Процессор можешь воткнуть один, оперативку другую, видеокарту третью, а про всякие RGB-подсветки, которые только электричество жрут, я вообще молчу.

И что делать? Писать конструктор на 15 параметров? Да ты с ума сошёл! Это ж будет как в том анекдоте: «Видел конструктор? — Нет, но ослышался». Получится телескопический конструктор, блядь, такой длинный, что в один экран не влезет. А если половина параметров не нужна? Придётся null пихать, а это, прости меня, чистой воды распиздяйство.

Вот для этого и придумали Строителя, ёпта! Суть проще пареной репы. Берёшь главный класс, например, Computer, и делаешь ему статический внутренний класс-помощника, ComputerBuilder. Этот помощник — он как тот самый Герасим, здоровый и молчаливый, который всё сделает, но по чёткому плану.

Смотри, как это выглядит в коде, не моргай:

public class Computer {
    // Вот это — железо, которое уже впаяно. Final, менять низя.
    private final String cpu;
    private final String ram;
    // А это — опциональное барахло, можно было и не ставить
    private final String storage;
    private final String gpu;

    // Конструктор прячем, чтобы только Builder мог им пользоваться. Хитрая жопа!
    private Computer(ComputerBuilder builder) {
        this.cpu = builder.cpu;
        this.ram = builder.ram;
        this.storage = builder.storage;
        this.gpu = builder.gpu;
    }

    // А вот и наш красавец, сам Builder!
    public static class ComputerBuilder {
        // Без этого — никуда. Как водка без закуски.
        private final String cpu;
        private final String ram;
        // А это уже на твой вкус. По умолчанию — что-то скромное.
        private String storage = "256GB SSD";
        private String gpu = "Integrated";

        // Заставляем указать самое главное с самого начала
        public ComputerBuilder(String cpu, String ram) {
            this.cpu = cpu;
            this.ram = ram;
        }

        // А дальше — магия! Каждый метод возвращает самого себя (this).
        // Это называется Fluent Interface, звучит умно, а делается просто.
        public ComputerBuilder storage(String storage) {
            this.storage = storage;
            return this;
        }

        public ComputerBuilder gpu(String gpu) {
            this.gpu = gpu;
            return this;
        }

        // И кульминация! Метод build(), который всё это собирает в кучу.
        public Computer build() {
            // Тут можно проверить, не долбоёб ли ты, и не забыл ли указать процессор.
            if (cpu == null || ram == null) {
                throw new IllegalArgumentException("CPU and RAM are required, мудак!");
            }
            return new Computer(this); // Возвращаем готовый, неизменяемый комп!
        }
    }
}

А теперь, внимание, как этим пользоваться. Это просто песня, а не код:

// Собираем игрового монстра
Computer gamingBeast = new Computer.ComputerBuilder("AMD Ryzen 7", "32GB DDR5")
        .storage("2TB NVMe")
        .gpu("NVIDIA RTX 4090") // Мечта ипотечника!
        .build(); // Всё, готово! Объект создан, поля final, менять нельзя.

// А это — офисный плебейский сишник
Computer officeSlave = new Computer.ComputerBuilder("Intel Celeron", "8GB")
        .build(); // storage и gpu остались по умолчанию. И так сойдёт!

Вот видишь? Красиво, читаемо, как будто инструкцию по сборке читаешь. Никаких null, никаких конструкторов на 10 страниц. А главное — объект после build() становится неизменяемым, как скала. В многопоточке это — просто бальзам на душу, терпения ебать ноль на синхронизацию тратить.

Конечно, сейчас все умники используют Lombok с аннотацией @Builder, чтобы не писать этот код вручную. Ну, это как готовую тушёнку брать вместо того, чтобы самого мяса тушить. Удобно, спору нет. Но понимать-то, как оно работает внутри, всё равно надо! А то будешь как тот чувак, который @Builder на Record навесил, а потом орет: «Почему не компилируется, блядь?!».

Так что запомни, дружок: Builder — это когда нужно собрать что-то сложное по кирпичикам, не превращая свою жизнь в ад с телескопическими конструкторами. Всё гениальное — просто, как Муму. Только топить его не надо.