Это вопрос о человеческом интерфейсе о объединении шаблона построителя шагов с расширенным или wizard шаблоны builder в creational DSL. Он использует свободный интерфейс, хотя он использует цепочку методов, а не каскадирование. То есть методы возвращают разные типы.
Im сталкивается с классом монстров, который имеет два конструктора, которые принимают смесь из целых чисел, строк и массива строк. Каждый конструктор имеет 10 параметров. Он также имеет около 40 опциональных сеттеров; некоторые из которых конфликтуют друг с другом, если они используются вместе. Его код конструкции выглядит примерно так:
Person person = Person("Homer","Jay", "Simpson","Homie", null, "black", "brown",
new Date(1), 3, "Homer Thompson", "Pie Man", "Max Power", "El Homo",
"Thad Supersperm", "Bald Mommy", "Rock Strongo", "Lance Uppercut", "Mr. Plow");
person.setClothing("Pants!!");
person.setFavoriteBeer("Duff");
person.setJobTitle("Safety Inspector");
Это в конечном итоге терпит неудачу, потому что получается, что оба любимого пива и название должности несовместимы. Вздох.
Реорганизация класса монстров не является вариантом. Его широко используется. Оно работает. Я просто не хочу смотреть, как он создается напрямую. Я хочу написать что-то чистое, что будет его кормить. Что-то, что будет следовать его правилам, не заставив разработчиков запомнить их.
В отличие от замечательных шаблонов построек, которые я изучал, эта вещь не приходит в ароматы или категории. Он требует некоторых полей все время и другие поля, когда это необходимо, а некоторые - в зависимости от того, что было задано ранее. Конструкторы не телескопируются. Они предоставляют два альтернативных способа, чтобы класс попал в одно и то же состояние. Они длинные и уродливые. То, что они хотят получать от них, меняется независимо.
Свободный строитель определенно заставит длинных конструкторов легче смотреть. Тем не менее, огромное количество опциональных сеттеров загромождает требуемые. И есть требование, чтобы каскадный свободный строитель не удовлетворял: компиляция времени.
Конструкторы заставляют разработчика явно добавлять обязательные поля, даже если их обнулить. Это теряется при использовании каскадного свободного конструктора. То же самое он потерял с сеттерами. Я хочу, чтобы разработчик не строил до тех пор, пока не будет добавлено каждое обязательное поле.
В отличие от многих шаблонов построителя, то, что Im после не является неизменяемым. Я покидаю класс, когда нашел его. Я хочу знать, что построенный объект находится в хорошем состоянии, просто посмотрев на код, который его создает. Без необходимости ссылаться на документацию. Это означает, что необходимо выполнить программист с помощью условно необходимых шагов.
Person makeHomer(PersonBuilder personBuilder){ //Injection avoids hardcoding implementation
return personBuilder
// -- These have good default values, may be skipped, and don't conflict -- //
.doOptional()
.addClothing("Pants!!") //Could also call addTattoo() and 36 others
// -- All fields that always must be set. @NotNull might be handy. -- //
.doRequired() //Forced to call the following in order
.addFirstName("Homer")
.addMiddleName("Jay")
.addLastName("Simpson")
.addNickName("Homie")
.addMaidenName(null) //Forced to explicitly set null, a good thing
.addEyeColor("black")
.addHairColor("brown")
.addDateOfBirth(new Date(1))
.addAliases(
"Homer Thompson",
"Pie Man",
"Max Power",
"El Homo",
"Thad Supersperm",
"Bald Mommy",
"Rock Strongo",
"Lance Uppercut",
"Mr. Plow")
// -- Controls alternatives for setters and the choice of constructors -- //
.doAlternatives() //Either x or y. a, b, or c. etc.
.addBeersToday(3) //Now can't call addHowDrunk("Hammered");
.addFavoriteBeer("Duff")//Now can’t call addJobTitle("Safety Inspector");
.doBuild() //Not available until now
;
}
Человек может быть создан после addBeersToday(), поскольку в этот момент вся информация о конструкторе известна, но не возвращается до doBuild().
public Person(String firstName, String middleName, String lastName,
String nickName, String maidenName, String eyeColor,
String hairColor, Date dateOfBirth, int beersToday,
String[] aliases);
public Person(String firstName, String middleName, String lastName,
String nickName, String maidenName, String eyeColor,
String hairColor, Date dateOfBirth, String howDrunk,
String[] aliases);
Эти параметры задают поля, которые никогда не должны оставляться со значениями по умолчанию. beersToday и howDrunk устанавливают одно и то же поле разными способами. favoriteBeer и jobTitle - это разные поля, но вызывают конфликты с тем, как используется класс, поэтому нужно установить только один. Они обрабатываются сеттерами, не являющимися конструкторами.
Метод doBuild()
возвращает объект Person
. Это единственный, который делает, и Person
- единственный тип, который он вернет. Когда он Person
полностью инициализируется.
На каждом шаге интерфейса возвращаемый тип не всегда одинаковый. Изменение типа - это то, как разработчик руководствуется этими шагами. Он предлагает только действующие методы. Метод doBuild()
не доступен, пока все необходимые шаги не будут завершены.
Префиксы do/add - это kludge, чтобы упростить запись, поскольку изменяющийся тип возвращаемого значения несоответствие с назначением и делает рекомендации по интеллекту в алфавитном порядке в затмении. Я подтвердил, что у intellij нет этой проблемы. Спасибо Нимхимпски.
Этот вопрос касается интерфейса, поэтому я буду принимать ответы, которые не обеспечивают реализацию. Но если вы знаете один, пожалуйста, поделитесь.
Если вы предложите альтернативный шаблон, пожалуйста, покажите его интерфейс. Используйте все входы из примера.
Если вы предлагаете использовать представленный здесь интерфейс или некоторые незначительные вариации, пожалуйста, защитите его от критики, например this.
То, что я действительно хочу знать, - это то, что большинство людей предпочтут использовать этот интерфейс для создания или другого. Это вопрос о человеческом интерфейсе. Это нарушает PoLA? Не беспокойтесь о том, как сложно было бы реализовать.
Однако, если вам интересны реализации:
Произошла неудачная попытка (не было достаточного количества состояний или было понятно, что vs не был установлен по умолчанию)
Реализация построителя шагов (недостаточно гибкая для нескольких конструкторов или альтернатив)
Усовершенствованный построитель (неподвижный лайнер, но имеет гибкие состояния)
Wizard builder (работает с forking, но не помнит путь для выбора конструктора)
Требование:
- Класс монстра (человека) уже закрыт для модификации и расширения; не обидчивый
Цели:
- Скрыть длинные конструкторы, так как класс монстра имеет 10 обязательных параметров
- Определите, какой конструктор должен вызывать на основе используемых альтернатив
- Запретить конфликтующие сеттеры
- Принудительные правила во время компиляции
Цель:
- Четко сигнализировать, когда значения по умолчанию неприемлемы