digital-garden/dev/architecture/Builder Pattern.md
Struchkov Mark 46e423c2db
All checks were successful
continuous-integration/drone/push Build is passing
Fluent API.md
2024-10-04 01:19:00 +03:00

319 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
aliases:
tags:
- maturity/🌱
date: 2024-10-04
zero-link:
parents:
linked:
---
Builder Pattern — это [[порождающий паттерн проектирования]], который используется для пошагового создания сложных объектов. Этот паттерн особенно полезен, когда объект может иметь множество конфигураций или параметров, которые делают его создание через конструкторы неудобным или даже невозможным.
Основные концепции
- **Разделение построения и представления**. Билдер позволяет отделить логику создания объекта от его конечной структуры. Это делает код более чистым и поддерживаемым.
- **Пошаговая сборка**. Позволяет добавлять параметры или части объекта последовательно, при этом сам процесс создания может контролироваться и меняться независимо от основной логики объекта.
- **Иммутабельность**. Паттерн билдер часто применяется для создания неизменяемых объектов. После сборки объекта его нельзя изменить, что улучшает предсказуемость и безопасность.
## Пример применения
Рассмотрим создание объекта `Car`, у которого много настроек, таких как тип двигателя, количество дверей, цвет и т.д.
Без использования паттерна «Билдер» мы можем столкнуться с такой проблемой: необходимо создавать различные конструкторы, что ухудшает читаемость и поддержку кода. Паттерн «Билдер» помогает избежать этого, при этом используя [[Fluent API|fluent API]] стиль — подход, при котором методы возвращают сам объект билдера, позволяя вызывать их цепочкой. Это делает код более выразительным и легким для чтения.
```java
public class Car {
private String engine;
private int doors;
private String color;
private Car(CarBuilder builder) {
this.engine = builder.engine;
this.doors = builder.doors;
this.color = builder.color;
}
public static class CarBuilder {
private String engine;
private int doors;
private String color;
public CarBuilder setEngine(String engine) {
this.engine = engine;
return this;
}
public CarBuilder setDoors(int doors) {
this.doors = doors;
return this;
}
public CarBuilder setColor(String color) {
this.color = color;
return this;
}
public Car build() {
return new Car(this);
}
}
}
```
Использование паттерна:
```java
Car car = new Car.CarBuilder()
.setEngine("V8")
.setDoors(4)
.setColor("Red")
.build();
System.out.println(car);
```
**Преимущества**
1. **Чистый код**. Конфигурация объектов становится ясной и понятной, даже если у объекта множество параметров.
2. **Гибкость в создании объектов**. Можно не указывать все параметры сразу, а добавлять их по мере необходимости, что делает процесс сборки более гибким.
3. **Поддержка иммутабельности**. Объекты могут быть неизменяемыми после создания, так как параметры устанавливаются только в процессе сборки.
4. **Минимизация перегрузок конструкторов**. Это позволяет избежать множества конструкторов для различных комбинаций параметров.
**Недостатки**
- **Усложнение кода**. Добавление класса-билдера может увеличить объем кода, особенно если объект не настолько сложен, чтобы оправдать использование паттерна.
- **Многословность**. Если объект требует только нескольких параметров, то использование билдера может казаться излишним и создавать ненужную многословность.
**Когда применять?**
- Когда объект имеет множество конфигураций и параметров.
- Когда нужен гибкий процесс создания объектов с возможностью пошагового добавления параметров.
- Когда объект должен быть неизменяемым, и после сборки его состояние не должно меняться.
## Продвинутый билдер
### Обязательные поля
Одной из распространенных проблем является ==отсутствие явного указания на обязательные поля== при использовании билдера. Если объект имеет поля, которые обязательны для заполнения (например, идентификатор или имя), но их установка не контролируется билдером, это может привести к созданию некорректных или невалидных объектов.
**Решение**:
- Обязательные поля можно передавать через конструктор билдера, чтобы их указание было обязательным. Остальные параметры можно указывать через цепочку методов.
Пример:
```java
public class Car {
private final String engine; // Обязательное поле
private final String model; // Обязательное поле
private int doors; // Необязательное поле
private String color; // Необязательное поле
// Приватный конструктор для сборки объекта через билдер
private Car(CarBuilder builder) {
this.engine = builder.engine;
this.model = builder.model;
this.doors = builder.doors;
this.color = builder.color;
}
// Статический метод для создания билдера с обязательными полями
public static CarBuilder builder(String engine, String model) {
return new CarBuilder(engine, model);
}
public static class CarBuilder {
private final String engine; // Обязательное поле
private final String model; // Обязательное поле
private int doors = 4; // Значение по умолчанию
private String color = "Black"; // Значение по умолчанию
// Конструктор билдера с обязательными полями
public CarBuilder(String engine, String model) {
if (engine == null || engine.isEmpty()) {
throw new IllegalArgumentException("Engine is required");
}
if (model == null || model.isEmpty()) {
throw new IllegalArgumentException("Model is required");
}
this.engine = engine;
this.model = model;
}
// Методы для установки необязательных полей
public CarBuilder setDoors(int doors) {
this.doors = doors;
return this;
}
public CarBuilder setColor(String color) {
this.color = color;
return this;
}
// Метод для сборки объекта Car
public Car build() {
return new Car(this);
}
}
}
```
Теперь обязательные поля передаются при создании билдера:
```java
Car car = Car.builder("V8", "Sedan") // Передача обязательных полей через статический метод
.setDoors(2) // Опциональные поля
.setColor("Red")
.build();
```
### Валидация создания объекта
Ещё одна частая проблема заключается в том, что во время процесса построения ==не проверяются ограничения на совместимость полей.== Например, не всегда проверяется корректность значений или логика взаимодействия между параметрами, что может привести к созданию некорректного объекта.
**Решение**:
Добавляйте в билдер логику валидации и проверки состояния перед созданием объекта. Это позволит убедиться, что все параметры совместимы и объект корректен.
Пример:
```java
public Car build() {
if (doors < 2 || doors > 6) {
throw new IllegalArgumentException("Invalid number of doors");
}
return new Car(this);
}
```
### Многократный вызов методов
Когда методы билдера вызываются несколько раз, каждый вызов может перезаписывать значение параметра, что может остаться незамеченным или вызвать непредсказуемое поведение.
Предположим, что у нас есть билдер для создания объекта `Car`, и метод для установки количества дверей (`setDoors`) был вызван дважды:
```java
Car car = new Car.CarBuilder("V8")
.setDoors(4)
.setDoors(2) // Этот вызов перезапишет предыдущее значение
.setColor("Red")
.build();
```
В результате объект car будет создан с двумя дверями, хотя программист мог ожидать, что будет 4 двери (из-за первого вызова). Такая ситуация особенно распространена, когда объект конфигурируется динамически, или когда несколько разработчиков работают с билдером, не зная всех деталей.
#### Возможные решения проблемы
**Введение проверок на повторный вызов.** Чтобы избежать многократных вызовов одного и того же метода, можно добавить логику проверки, которая будет отслеживать, был ли метод уже вызван ранее. Если метод вызывается повторно, можно выбросить исключение или проигнорировать повторный вызов.
```java
public static class CarBuilder {
private String engine;
private int doors;
private String color;
private boolean doorsSet = false; // Флаг, указывающий на то, что метод setDoors уже был вызван
public CarBuilder(String engine) {
this.engine = engine;
}
public CarBuilder setDoors(int doors) {
if (doorsSet) {
throw new IllegalStateException("Doors can only be set once");
}
this.doors = doors;
doorsSet = true;
return this;
}
public CarBuilder setColor(String color) {
this.color = color;
return this;
}
public Car build() {
return new Car(this);
}
}
```
**Логирование перезаписи параметров.** Если необходимо разрешить многократные вызовы методов, но при этом важно отслеживать перезапись параметров, можно добавлять логирование, чтобы было видно, когда параметр перезаписывается новым значением.
```java
public CarBuilder setDoors(int doors) {
if (this.doors != 0) {
System.out.println("Warning: Doors value is being overwritten from " + this.doors + " to " + doors);
}
this.doors = doors;
return this;
}
```
**Использование** [[Fluent API#Step building|Fluent API Step building]]. Позволит конфигурировать объект в определенной последовательности.
```java
public class Car {
private final String engine; // Обязательное поле
private final int doors; // Обязательное поле
private final String color; // Обязательное поле
// Приватный конструктор для сборки через пошаговую сборку
private Car(String engine, int doors, String color) {
this.engine = engine;
this.doors = doors;
this.color = color;
}
// Интерфейс для первого шага: выбор двигателя
public interface EngineStep {
DoorsStep setEngine(String engine);
}
// Интерфейс для второго шага: выбор дверей
public interface DoorsStep {
ColorStep setDoors(int doors);
}
// Интерфейс для третьего шага: выбор цвета
public interface ColorStep {
BuildStep setColor(String color);
}
// Интерфейс для финального шага: завершение сборки
public interface BuildStep {
Car build();
}
// Класс, который реализует пошаговую сборку
public static class Builder implements EngineStep, DoorsStep, ColorStep, BuildStep {
private String engine;
private int doors;
private String color;
@Override
public DoorsStep setEngine(String engine) {
this.engine = engine;
return this;
}
@Override
public ColorStep setDoors(int doors) {
this.doors = doors;
return this;
}
@Override
public BuildStep setColor(String color) {
this.color = color;
return this;
}
@Override
public Car build() {
return new Car(engine, doors, color);
}
}
// Метод для запуска пошаговой сборки
public static EngineStep builder() {
return new Builder();
}
}
```
***
## Мета информация
**Область**:: [[../../meta/zero/00 Разработка|00 Разработка]]
**Родитель**:: [[Порождающий паттерн проектирования]]
**Источник**::
**Создана**:: [[2024-10-04]]
**Автор**::
### Дополнительные материалы
-
### Дочерние заметки
<!-- QueryToSerialize: LIST FROM [[]] WHERE contains(Родитель, this.file.link) or contains(parents, this.file.link) -->