Паттерн Builder в Java на котиках

от автора

Привет, друзья! Сегодня рассмотрим реализацию паттерна «Строитель» в Java. Паттерн может превратить необъятный хаос параметров в аккуратную и управляемую конструкцию. И всё это на примере наших пушистых друзей — котиков.

Коротко про сам паттерн

Итак, представьте: вы решили создать класс Cat. Только не просто кота, а настоящего гурмана, пушистого и с множеством уникальных характеристик. Имя, цвет, возраст, вес, степень пушистости, громкость мяуканья и, конечно же, любимые лакомства. Как не запутаться в этом калейдоскопе параметров?

Тут может помочь паттерн «Строитель» или как его называют на английском Builder. Технически, паттерн позволяет разделить процесс создания объекта от его представления. То есть можно создавать различные представления одного и того же объекта, изменяя только шаги строительства. К примеру в том же примере с котом, можно легко создавать разных котиков с разными наборами характеристик, не дублируя код и не усложняя конструкцию классов, это мы увидим в примерах кода ниже.

Кроме того, паттерн способствует поддержанию принципа единственной ответственности из SOLID. Допустим, класс Cat отвечает только за хранение данных, а отдельный строитель — за их инициализацию. Это разграничение обязанностей делает код более модульным и тем самым удобным для тестирования.

Пример на котиках

Давайте сразу к делу! Представьте себе класс Cat с множеством параметров: имя, цвет, возраст, вес, пушистость, громкость мяуканья и любимые лакомства. Для такого количества параметров идеально подходит паттерн «Строитель». И вот код:

import java.util.Collections; import java.util.ArrayList; import java.util.List;  public class Cat {     private final String name;     private final String color;     private final int age;     private final double weight;     private final boolean isFluffy;     private final int meowVolume;     private final List<String> favoriteFoods;      private Cat(Builder builder) {         this.name = builder.name;         this.color = builder.color;         this.age = builder.age;         this.weight = builder.weight;         this.isFluffy = builder.isFluffy;         this.meowVolume = builder.meowVolume;         this.favoriteFoods = Collections.unmodifiableList(new ArrayList<>(builder.favoriteFoods));     }      // Статический внутренний класс Builder     public static class Builder {         private final String name; // обязательный параметр         private String color = "Grey"; // значение по умолчанию         private int age = 0;         private double weight = 0.0;         private boolean isFluffy = false;         private int meowVolume = 1;         private List<String> favoriteFoods = new ArrayList<>();          public Builder(String name) {             if (name == null || name.trim().isEmpty()) {                 throw new IllegalArgumentException("Name cannot be null or empty.");             }             this.name = name;         }          public Builder color(String color) {             if (color == null || color.trim().isEmpty()) {                 throw new IllegalArgumentException("Color cannot be null or empty.");             }             this.color = color;             return this;         }          public Builder age(int age) {             if (age < 0 || age > 30) {                 throw new IllegalArgumentException("Age must be between 0 and 30.");             }             this.age = age;             return this;         }          public Builder weight(double weight) {             if (weight < 0 || weight > 15) {                 throw new IllegalArgumentException("Weight must be between 0 and 15 kg.");             }             this.weight = weight;             return this;         }          public Builder isFluffy(boolean isFluffy) {             this.isFluffy = isFluffy;             return this;         }          public Builder meowVolume(int meowVolume) {             if (meowVolume < 0 || meowVolume > 10) {                 throw new IllegalArgumentException("Meow volume must be between 0 and 10.");             }             this.meowVolume = meowVolume;             return this;         }          public Builder addFavoriteFood(String food) {             if (food == null || food.trim().isEmpty()) {                 throw new IllegalArgumentException("Food cannot be null or empty.");             }             this.favoriteFoods.add(food);             return this;         }          public Cat build() {             return new Cat(this);         }     }      @Override     public String toString() {         return "Cat{" +                 "name='" + name + '\'' +                 ", color='" + color + '\'' +                 ", age=" + age +                 ", weight=" + weight +                 ", isFluffy=" + isFluffy +                 ", meowVolume=" + meowVolume +                 ", favoriteFoods=" + (favoriteFoods.size() > 5 ? favoriteFoods.subList(0, 5) + "..." : favoriteFoods) +                 '}';     } }

Пару слов:

Все поля класса Cat объявлены как final, что гарантирует их неизменность после создания объекта. А внутренний статический класс Builder содержит те же поля, что и Cat, но с возможностью установки значений через методы.

Теперь создадим несколько разных котиков, придавая им уникальные параметры:

public class Main {     public static void main(String[] args) {         // Создаем кота с обязательными и несколькими опциональными параметрами         Cat vasiliy = new Cat.Builder("Vasiliy")                 .color("White")                 .age(3)                 .weight(4.2)                 .isFluffy(true)                 .meowVolume(5)                 .addFavoriteFood("Tuna")                 .addFavoriteFood("Salmon")                 .addFavoriteFood("Chicken")                 .addFavoriteFood("Milk")                 .addFavoriteFood("Cheese")                 .addFavoriteFood("Fish")                 .build();          // Создаем кота только с обязательным параметром         Cat murka = new Cat.Builder("Murka")                 .weight(3.8)                 .build();          // Создаем кота с несколькими параметрами         Cat barsik = new Cat.Builder("Barsik")                 .color("Brown")                 .age(5)                 .isFluffy(false)                 .addFavoriteFood("Beef")                 .build();          System.out.println(vasiliy);         System.out.println(murka);         System.out.println(barsik);     } }

Вывод:

Cat{name='Vasiliy', color='White', age=3, weight=4.2, isFluffy=true, meowVolume=5, favoriteFoods=[Tuna, Salmon, Chicken, Milk, Cheese...]} Cat{name='Murka', color='Grey', age=0, weight=3.8, isFluffy=false, meowVolume=1, favoriteFoods=[]} Cat{name='Barsik', color='Brown', age=5, weight=0.0, isFluffy=false, meowVolume=1, favoriteFoods=[Beef]}

В итоге мы получаем важную информацию:

  • Vasiliy: настоящий гурман! Белый, пушистый, возраст 3 года, громкое мяуканье и обожает тунцу и лосося.

  • Murka: небольшая, но крепкая. Весит 3.8  кг, остальные параметры по умолчанию.

  • Barsik: коричневый кот, не особо пушистый, весит 6.2 кг и любит курицу.

Таким образом, паттерн позволяет создавать объекты с различными конфигурациями, избегая перегруженных конструкторов.

Когда НЕ стоит использовать «Строитель»?

Паттерн «Строитель» — это мощный инструмент, но, как и все, он имеет свои границы.

  1. Простые объекты с 2–3 параметрами: если объект имеет всего несколько полей, использование простого конструктора или сеттеров может быть более предпочтительным и менее избыточным.

    Например: класс Point с полями x и y.

  2. Слишком динамичные объекты: если объект предполагает частые изменения после создания, иммутабельность может стать препятствием. В таких случаях лучше использовать сеттеры или другие подходы.

    Пример: класс MutableConfig, который постоянно изменяется во время работы приложения.

  3. Необходимость создания наследников: если нужно создавать множество наследуемых версий объекта с различными конфигурациями, паттерн «Строитель» может усложнить код. В таких случаях можно глянуть такой паттерн как «Абстрактная фабрика».

Заключение

Этот паттерн — отличный способ собрать сложные объекты без лишней путаницы. Пробуйте, внедряйте, и если есть вопросы — задавайте их в комментариях!

Приглашаю вас на страницу курса «Java Developer. Advanced», где вы сможете посмотреть записи прошедших вебинаров, а также зарегистрироваться на бесплатный вебинар по теме: «Знакомство с виртуальными потоками Java».


ссылка на оригинал статьи https://habr.com/ru/articles/858032/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *