Недавно я удалил свой Java- ответ на Code Review , который начался так:
private Person(PersonBuilder builder) {
Стоп. Красный флаг. PersonBuilder будет строить Person; это знает о Человеке. Класс Person не должен ничего знать о PersonBuilder - это просто неизменный тип. Вы создали круговую связь здесь, где A зависит от B, который зависит от A.
Человек должен просто принять его параметры; клиент, который хочет создать Человека, не создавая его, должен иметь возможность сделать это.
Я получил удар вниз и сказал, что (цитируя) Красный флаг, почему? Реализация здесь имеет ту же форму, которую продемонстрировал Джошуа Блох в своей книге «Эффективная Java» (пункт № 2).
Итак, похоже, что Единственно верный способ реализации Pattern Builder в Java - это сделать построитель вложенным типом (хотя это и не тот вопрос, о котором идет речь), а затем создать продукт (класс создаваемого объекта). ) возьмите зависимость от строителя , вот так:
private StreetMap(Builder builder) { // Required parameters origin = builder.origin; destination = builder.destination; // Optional parameters waterColor = builder.waterColor; landColor = builder.landColor; highTrafficColor = builder.highTrafficColor; mediumTrafficColor = builder.mediumTrafficColor; lowTrafficColor = builder.lowTrafficColor; }
Одна и та же страница Википедии для того же шаблона Builder имеет совершенно другую (и гораздо более гибкую) реализацию для C #:
//Represents a product created by the builder public class Car { public Car() { } public int Wheels { get; set; } public string Colour { get; set; } }
Как вы можете видеть, продукт здесь ничего не знает о Builder
классе, и, несмотря на все заботы, он может быть создан прямым вызовом конструктора, абстрактной фабрикой ... или конструктором - насколько я понимаю, продукту творческого паттерна никогда не нужно ничего знать о том, что его создает.
Мне предоставили встречный аргумент (который явно защищен в книге Блоха) о том, что шаблон конструктора можно использовать для переделки типа, в котором конструктор будет раздут с десятками необязательных аргументов. Поэтому вместо того, чтобы придерживаться того, что я думал, я знал, что немного исследовал этот сайт и обнаружил, что, как я и подозревал, этот аргумент не выдерживает критики .
Так в чем же дело? Зачем придумывать изощренные решения проблемы, которой вообще не должно быть? Если мы на минуту снимаем Джошуа Блоха с пьедестала, можем ли мы придумать одну вескую и уважительную причину для объединения двух конкретных типов и назвать это лучшей практикой?
Это все пахнет грузо-культовым программированием для меня.
источник
Ответы:
Я не согласен с вашим утверждением, что это красный флаг. Я также не согласен с вашим представлением контрапункта, что существует один правильный способ представления паттерна Builder.
Для меня есть один вопрос: является ли Builder необходимой частью API типа? Здесь есть PersonBuilder необходимая часть API Person?
Вполне разумно, что проверяемый на достоверность, неизменный и / или тесно инкапсулированный класс Person обязательно будет создан с помощью предоставляемого им построителя (независимо от того, является ли этот построитель вложенным или смежным). При этом Person может сохранять все свои поля приватными или package-private и final, а также оставлять класс открытым для модификации, если он находится в стадии разработки. (Это может в конечном итоге закрыто для модификации и открыты для расширения, но это не важно сейчас, и особенно спорный для неизменяемых объектов данных.)
Если это тот случай, когда Person обязательно создается с помощью поставляемого PersonBuilder как часть единого проекта API уровня пакета, тогда циклическое связывание просто отлично, и красный флаг здесь не гарантируется. Примечательно, что вы назвали это неопровержимым фактом, а не мнением или выбором дизайна API, так что это могло привести к неправильному ответу. Я бы не проголосовал против вас, но я не виню кого-либо за это, и оставлю оставшуюся часть обсуждения «что оправдывает понижение» справочному центру Code Review и мета . Случаются отрицательные голоса; это не "пощечина".
Конечно, если вы хотите оставить много способов открыть объект Person, то PersonBuilder может стать потребителем или утилитой.класса Person, от которого Person не должен напрямую зависеть. Этот выбор кажется более гибким - кому бы не хотелось больше вариантов создания объектов? - но для того, чтобы придерживаться гарантий (таких как неизменяемость или сериализуемость), внезапно Человек должен представить другой тип техники публичного создания, такой как очень длинный конструктор , Если конструктор предназначен для представления или замены конструктора множеством необязательных полей или конструктора, открытого для модификации, тогда длинный конструктор класса может быть деталью реализации, которую лучше оставить скрытой. (Также имейте в виду, что официальный и необходимый Строитель не запрещает вам писать свою собственную строительную утилиту, просто ваша строительная утилита может использовать Строителя как API, как в моем первоначальном вопросе выше.)
PS: Я отмечаю, что в приведенном вами примере проверки кода есть неизменный Персона, но в контрпримере из Википедии перечислены изменяемые машины с геттерами и сеттерами. Может быть легче увидеть необходимые машины, опущенные в автомобиле, если вы сохраняете язык и инварианты согласованными между ними.
источник
String(StringBuilder)
конструктор, который, похоже, подчиняется тому «принципу», когда для типа нормально иметь зависимость от своего конструктора. Я был ошеломлен, когда увидел перегрузку конструктора. Это не так, какString
имеет 42 параметров конструктора ...toString()
универсален.Я понимаю, как это может раздражать, когда в дискуссии используется «обращение к власти». Аргументы должны быть основаны на их собственной ИМО, и хотя нет ничего неправильного в том, чтобы указывать на советы такого уважаемого человека, это не может считаться полным аргументом сам по себе. Мы знаем, что Солнце путешествует по земле, потому что Аристотель сказал так ,
Сказав это, я думаю предпосылка вашего аргумента вроде того же. Мы никогда не должны связывать два конкретных типа и называть это лучшей практикой, потому что ... Кто-то так сказал?
Чтобы аргумент о том, что связывание проблематично, является действительным, должны быть определенные проблемы, которые он создает. Если этот подход не подпадает под эти проблемы, то вы не можете логически утверждать, что эта форма связи является проблематичной. Поэтому, если вы хотите высказать свою точку зрения, я думаю, вам нужно показать, как этот подход зависит от этих проблем и / или как разделение компоновщика улучшит дизайн.
Изо всех сил я изо всех сил пытаюсь увидеть, как совместный подход создаст проблемы. Конечно, классы полностью связаны, но конструктор существует только для создания экземпляров класса. Другой способ взглянуть на это - расширение конструктора.
источник
String
делает с перегрузкой конструктор , принимающийStringBuilder
параметр (хотя это спорно лиStringBuilder
это строитель , но это другая история).VBE
макет с окнами, активной панелью кода, проектом и компонентом с модулем, который содержит предоставленную вами строку. Без этого я не знаю, как я смогу написать 80% тестов в Rubberduck , по крайней мере, не ударяя себя в глаза каждый раз, когда я смотрю на них.Чтобы ответить, почему тип связан с компоновщиком, необходимо понять, почему кто-либо вообще использует компоновщик. В частности: Bloch рекомендует использовать конструктор, когда у вас есть большое количество параметров конструктора. Это не единственная причина для использования строителя, но я полагаю, что это наиболее распространенный вариант. Короче говоря, компоновщик является заменой этих параметров конструктора, и часто компоновщик объявляется в том же классе, что и компоновка. Таким образом, включающий класс уже знает о сборщике, и передача его в качестве аргумента конструктора не меняет этого. Вот почему тип будет связан с его строителем.
Почему наличие большого количества параметров означает, что вы должны использовать конструктор? Что ж, наличие большого количества параметров не является чем-то необычным в реальном мире и не нарушает принцип единой ответственности. Это также отстой, когда у вас есть десять параметров String и вы пытаетесь вспомнить, какие из них являются какими, и является ли это пятой или шестой строкой, которая должна быть нулевой. Строитель облегчает управление параметрами, а также может делать другие интересные вещи, о которых вы должны прочитать в книге, чтобы узнать о них.
Когда вы используете конструктор, который не связан с его типом? Как правило, когда компоненты объекта не доступны сразу, и объектам, имеющим эти части, не нужно знать о типе, который вы пытаетесь создать, или вы добавляете конструктор для класса, над которым у вас нет контроля ,
источник
Person
Класс не знает , что его создание. Он имеет только конструктор с параметром, и что? Имя типа параметра заканчивается на ... Builder, который на самом деле ничего не форсирует. В конце концов, это просто имя.Строитель прославил 1 объект конфигурации. И объект конфигурации на самом деле просто группирует свойства, которые принадлежат друг другу. Если они принадлежат друг другу только с целью передачи конструктору класса, пусть будет так! Оба
origin
иdestination
изStreetMap
примера имеют типPoint
. Можно ли было передать отдельные координаты каждой точки на карту? Конечно, но свойстваPoint
принадлежат друг другу, плюс вы можете иметь на них всевозможные методы, которые помогают в их построении:1 Разница заключается в том, что компоновщик - это не просто объект простых данных, он допускает эти цепные вызовы сеттера. В
Builder
основном это касается того, как строить себя.Теперь давайте добавим немного комбинации команд в микс, потому что если вы посмотрите внимательно, то на самом деле строитель - это команда:
Особая часть для строителя заключается в том, что:
То, что передается
Person
конструктору, может его построить, но это не обязательно. Это может быть простой старый объект конфигурации или компоновщик.источник
Нет, они совершенно не правы, а вы абсолютно правы. Эта модель строителя просто глупая. Это не дело Человека, откуда берутся аргументы конструктора. Конструктор - это просто удобство для пользователей и не более того.
источник
PersonBuilder
фактически является существенной частьюPerson
API. В таком виде, этот ответ не доказывает свою правоту.