Допустим, вы встретили класс с именем 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)