Допустим, вы встретили класс с именем PollutantEntry, в конструкторе которого есть длинный список параметров:

class PollutantEntry {
    String country;
    String state;
    String city;
    String place;
    LocalDateTime localDateTime;
    Float average;
    Float max;
    Float min;
    String pollutant;

    public PollutantEntry(String country, String state, String city, String place, LocalDateTime localDateTime, Float average, Float max, Float min, String pollutant) {
        this.country = country;
        this.state = state;
        this.city = city;
        this.place = place;
        this.localDateTime = localDateTime;
        this.average = average;
        this.max = max;
        this.min = min;
        this.pollutant = pollutant;
    }
}

С такими списками параметров возникает много проблем:

а) Код вызывающего абонента, который вызывает конструктор, часто нечитаем. Это, конечно, не очень красивый код:

new PollutantEntry(readingEntries[0], readingEntries[1],  
   readingEntries[2], readingEntries[3], readingTime,       processFloat(readingEntries[5]), processFloat(readingEntries[6]), processFloat(readingEntries[7]), readingEntries[8]);

(Проверьте перегруженные конструкторы в java.util.Date, например, здесь.)

б) Многие поля часто являются необязательными. Попытка учесть необязательные параметры через вызовы перегруженных конструкторов только усугубляет ситуацию с 8–10 перегруженными конструкторами вроде этого:

public PollutantEntry(String state, String city, String place, LocalDateTime localDateTime, Float average, Float max, Float min, String pollutant) {
    this("India", state, city, place, localDateTime, average, max, min, pollutant);
}

Опять же, совсем не красиво.

c) Когда задействован код проверки, тело метода конструктора может стать довольно большим и сложным с высокой цикломатической сложностью.

Вы часто будете видеть группы параметров, которые всегда передаются и используются вместе («группы данных») - это еще один запах.

Итог: Конечно, такой код плохо пахнет и требует исправления.

Есть несколько подходов к борьбе с запахом «длинного списка параметров в конструкторах».

Вариант 1. Используйте шаблон компоновщика

Вместо передачи параметров в качестве аргументов создайте объект, вызвав методы, представив шаблон Builder. Фактически, этот рефакторинг настолько прост и распространен, что среды IDE даже автоматизируют его. Вот пример из IntelliJ IDEA:

Выберите конструктор с длинным списком параметров - ›« Рефакторинг »-› «Заменить конструктор на Builder…»

И выберите «Рефакторинг» во всплывающем окне:

Вот и все - два клика, и у вас есть класс конструктора, созданный для вас:

import java.time.LocalDateTime;

public class PollutantEntryBuilder {
    private String country;
    private String state;
    private String city;
    private String place;
    private LocalDateTime localDateTime;
    private Float average;
    private Float max;
    private Float min;
    private String pollutant;

    public PollutantEntryBuilder setCountry(String country) {
        this.country = country;
        return this;
    }

    public PollutantEntryBuilder setState(String state) {
        this.state = state;
        return this;
    }

    public PollutantEntryBuilder setCity(String city) {
        this.city = city;
        return this;
    }

    public PollutantEntryBuilder setPlace(String place) {
        this.place = place;
        return this;
    }

    public PollutantEntryBuilder setLocalDateTime(LocalDateTime localDateTime) {
        this.localDateTime = localDateTime;
        return this;
    }

    public PollutantEntryBuilder setAverage(Float average) {
        this.average = average;
        return this;
    }

    public PollutantEntryBuilder setMax(Float max) {
        this.max = max;
        return this;
    }

    public PollutantEntryBuilder setMin(Float min) {
        this.min = min;
        return this;
    }

    public PollutantEntryBuilder setPollutant(String pollutant) {
        this.pollutant = pollutant;
        return this;
    }

    public PollutantEntry createPollutantEntry() {
        return new PollutantEntry(country, state, city, place, localDateTime, average, max, min, pollutant);
    }
}

Более того, код звонящего автоматически заменяется звонком строителю!

PollutantEntry pollutantEntry =
        new PollutantEntryBuilder()
             .setCountry(readingEntries[0])
             .setState(readingEntries[1])
             .setCity(readingEntries[2])
             .setPlace(readingEntries[3])
             .setLocalDateTime(readingTime)
             .setAverage(processFloat(readingEntries[5]))
             .setMax(processFloat(readingEntries[6]))
             .setMin(processFloat(readingEntries[7]))
             .setPollutant(readingEntries[8])
             .createPollutantEntry();

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

Вариант 2. Выполните рефакторинг групп данных с помощью абстракций

Давайте теперь посмотрим на исходный объект PollutantEntry. Здесь есть три логических объекта - местоположение, время и показания на основе названия загрязнителя. Из них сведения о местонахождении и загрязнителях можно сделать отдельными абстракциями. Давайте сделаем это:

public class Location {
    private String country;
    private String state;
    private String city;
    private String place;
    public Location(String country, String state, String city, String place) {
        this.country = country;
        this.state = state;
        this.city = city;
        this.place = place;
    }
}
public class PollutionData {
    private String avg;
    private String max;
    private String min;
    private String pollutant;

    public PollutionData(String avg, String max, String min, String pollutant) {
        this.avg = avg;
        this.min = min;
        this.max = max;
        this.pollutant = pollutant;
    }
}
public class PollutantEntry {
    private Location location;
    private LocalDateTime lastUpdate;
    private PollutionData pollutionData;

    public PollutantEntry(Location location, LocalDateTime lastUpdate, PollutionData pollutionData) {
        this.location = location;
        this.lastUpdate = lastUpdate;
        this.pollutionData = pollutionData;
    }
}

Хм, это избавляет от длинного списка параметров в конструкторе и выглядит элегантно. Но можем ли мы сделать лучше ?!

Вариант 3. Создание абстракций и с помощью Builder

При таком подходе давайте создадим абстракции и с помощью шаблона Строитель, как показано ниже:

public class PollutantEntry {
    private Location location;
    private LocalDateTime lastUpdate;
    private PollutionData pollutionData;

   /* The Builder class corresponds to the PollutantEntry - it helps create a Pollutant entry object given the location object, time of reading, and the actual pollution reading */
    public static class Builder {
        PollutantEntry pollutantEntry = new PollutantEntry();

        public Builder() {
        }

        public Builder location(Location location) {
            pollutantEntry.location = location;
            return this;
        }

        public Builder lastUpdate(LocalDateTime lastUpdate) {
            pollutantEntry.lastUpdate = lastUpdate;
            return this;
        }

        public Builder pollutionData(PollutionData pollutionData) {
            pollutantEntry.pollutionData = pollutionData;
            return this;
        }

        public PollutantEntry build() {
            return pollutantEntry;
        }
    }

В этом случае у нас нет конструктора (или мы делаем конструктор закрытым) и выбираем статический вложенный класс Builder. Теперь код вызывающего абонента выглядит так:

PollutantEntry pollutantEntry =
        new PollutantEntry.Builder()
                          .location(location)
                          .lastUpdate(time)
                          .pollutionData(pollutionData)
                          .build();

Ах, мне это нравится - коротко, мило и элегантно - как и должен быть любой код :-)

Пример использования паттерна Builder в библиотеке классов Java находится здесь:

Calendar calendar = new Calendar.Builder()
                        .setDate(2019, 9, 10)
                        .build();

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

Резюме

Длинный список параметров в конструкторе - это всем известный дизайнерский запах. Чтобы избавиться от этого запаха, не ищите решения в перегруженных конструкторах - я бы сказал, что это другой запах! Скорее обращайте внимание на сгустки данных в параметрах создания абстракций. Также проверьте, может ли помочь введение шаблона Строитель. А еще лучше подумайте, можете ли вы использовать оба варианта - введение абстракций и шаблон Builder - которые помогают преобразовать этот запах в более чистый код.

(Написано Ганешем Самартьямом, соучредителем, KonfHub Technologies LLP)