Паттерн Builder для продвинутых



Начав обсуждать билдер, остановиться сложно. Я и не буду:) Сегодня подсвечу пару интересных моментов и один приём, который точно пригодится на практике.



Сразу к делу:



1️⃣ Билдер может содержать сложную логику, а не просто копить будущие поля



Например:

🔸 Собирать одну информацию, отдавать объекту другую

🔸 Возвращать любые классы и объекты, даже прокси и уже созданные

🔸 Проводить операции внутри метода build



Всё это при умелом использовании создаёт симпатичное и лаконичное API.



Пример 1: класс StringBuilder



Гораздо интереснее, чем кажется:

▫️ Копит данные в изменяемом массиве, на выходе отдаёт неизменяемую строку

▫️ Методы append можно вызывать сколько угодно раз

▫️ Есть дополнительные методы вроде reverse или delete



В результате получаем удобный и функциональный класс. Ломбок такой билдер не соберёт, чатЖПТ такое не придумает:)



Пример 2: класс HttpClient



В самом простом варианте код выглядит так:

HttpClient client = HttpClient.newBuilder().build();


🤔 Зачем тут билдер? Почему просто не сделать new HttpClient()?



Потому что внутри build происходит такая магия:

SingleFacadeFactory facadeFactory = new SingleFacadeFactory();

HttpClientImpl impl = new HttpClientImpl(builder, facadeFactory);

impl.start();



return facadeFactory.facade;


Тут прячется установка работы с сетью в 200+ строк кода и механизм по завершению работы с сетью, когда объект HttpClient станет не нужен. Хотя объект работает с ресурсами, его не надо помещать в блок try-with-resources.



Мелочь, а приятно🥰



И вторая классная особенность:



2️⃣ Билдер может сделать несколько объектов на базе переданных данных



Часто помогает при написании тестов.



Пример: тестируем работу с классом Account, в котором много полей. Можно в каждом тесте создавать тестовые объекты с нуля и копипастить километр кода. Или поступить иначе:



▫️ Сделать общий для всех тестов билдер, но build() не вызывать

▫️ В каждом тесте доставить нужные поля и построить новый объект



Получается так:

// общее поле c базовой информацией

Account.Builder accBuilder = Account.builder().INN(…).KPP(…)



// в тесте, где важен БИК:

Account acc = accBuilder.RCBIC(123).build();



// в тесте, где нужно название банка

Acccount acc = accBuilder.bankName("green").build();


Это короче, чем в каждом тесте создавать объект с нуля. Плюс сразу видно "главное" для теста поле.



Приём подходит не всегда, но иногда здорово улучшает читаемость.



Итого



Метод build может содержать сложную логику: от проверки параметров до шифрований и преобразований

Builder может создать несколько объектов на основе переданных данных



Возьмите эти свойства на заметку, в умелых руках паттерн Builder делает код проще и удобнее🔥