При использовании метода цепочки, как:
var car = new Car().OfBrand(Brand.Ford).OfModel(12345).PaintedIn(Color.Silver).Create();
может быть два подхода:
Повторно используйте тот же объект, например так:
public Car PaintedIn(Color color) { this.Color = color; return this; }
Создавайте новый объект типа
Car
на каждом шаге, например так:public Car PaintedIn(Color color) { var car = new Car(this); // Clone the current object. car.Color = color; // Assign the values to the clone, not the original object. return car; }
Первый ошибся или это скорее личный выбор разработчика?
Я считаю, что его первый подход может быстро вызвать интуитивный / вводящий в заблуждение код. Пример:
// Create a car with neither color, nor model.
var mercedes = new Car().OfBrand(Brand.MercedesBenz).PaintedIn(NeutralColor);
// Create several cars based on the neutral car.
var yellowCar = mercedes.PaintedIn(Color.Yellow).Create();
var specificModel = mercedes.OfModel(99).Create();
// Would `specificModel` car be yellow or of neutral color? How would you guess that if
// `yellowCar` were in a separate method called somewhere else in code?
Есть предположения?
coding-style
readability
method-chaining
Арсений Мурзенко
источник
источник
var car = new Car(Brand.Ford, 12345, Color.Silver);
?Ответы:
Я бы поместил свободный API в его собственный класс "builder" отдельно от объекта, который он создает. Таким образом, если клиент не хочет использовать свободный API, вы все равно можете использовать его вручную, и он не загрязняет объект домена (придерживаясь принципа единой ответственности). В этом случае будет создано следующее:
Car
который является объектом доменаCarBuilder
который держит свободный APIИспользование будет таким:
CarBuilder
Класс будет выглядеть следующим образом (я использую C # именования , здесь):Обратите внимание, что этот класс не будет потокобезопасным (каждому потоку потребуется собственный экземпляр CarBuilder). Также обратите внимание, что хотя свободный интерфейс API является действительно классной концепцией, он, вероятно, является излишним для создания простых доменных объектов.
Эта сделка более полезна, если вы создаете API для чего-то гораздо более абстрактного и более сложного в настройке и исполнении, поэтому он отлично работает в модульном тестировании и в структурах DI. Вы можете увидеть некоторые другие примеры в разделе Java статьи Википедии о свободном интерфейсе с объектами персистентности, обработки дат и фиктивных объектов.
РЕДАКТИРОВАТЬ:
Как отмечено в комментариях; вы можете сделать класс Builder статическим внутренним классом (внутри Car), и Car можно сделать неизменным. Этот пример того, как автомобиль был неизменным, кажется немного глупым; но в более сложной системе, где вы абсолютно не хотите изменять содержимое построенного объекта, вы можете захотеть это сделать.
Ниже приведен пример того, как сделать статический внутренний класс и обработать создание неизменного объекта, который он создает:
Использование будет следующим:
Редактировать 2: Пит в комментариях сделал сообщение в блоге об использовании компоновщиков с лямбда-функциями в контексте написания модульных тестов со сложными объектами домена. Это интересная альтернатива, чтобы сделать строителя немного более выразительным.
В случае, если
CarBuilder
вам нужен этот метод вместо:Который может быть использован как это:
источник
build()
(илиBuild()
), а не имя типа, который он создает (Car()
в вашем примере). Кроме того, еслиCar
это действительно неизменяемый объект (например, все его поляreadonly
), то даже создатель не сможет изменить его, поэтомуBuild()
метод становится ответственным за создание нового экземпляра. Один из способов сделать это -Car
иметь только один конструктор, который принимает в качестве аргумента Builder; тогдаBuild()
метод может простоreturn new Car(this);
.Это зависит от.
Является ли ваш автомобиль Entity или объект Value ? Если автомобиль является сущностью, то важна идентификация объекта, поэтому вы должны вернуть ту же ссылку. Если объект является объектом значения, он должен быть неизменным, то есть единственный способ - каждый раз возвращать новый экземпляр.
Примером последнего будет класс DateTime в .NET, который является объектом значения.
Однако, если модель является сущностью, мне нравится ответ Спойка об использовании класса builder для построения вашего объекта. Другими словами, этот пример, который вы привели, имеет смысл только в том случае, если Car является ценным объектом.
источник
Создайте отдельный статический внутренний конструктор.
Используйте обычные аргументы конструктора для обязательных параметров. И свободно API для необязательно.
Не создавайте новый объект при установке цвета, если вы не переименуете метод NewCarInColour или что-то подобное.
Я хотел бы сделать что-то подобное с этим брендом, как требуется, а остальное необязательно (это java, но ваш выглядит как javascript, но почти уверен, что они взаимозаменяемы с небольшим количеством выбора):
источник
Самое главное, что какое бы решение вы ни выбрали, оно четко указано в названии метода и / или комментарии.
Стандартов нет, иногда метод возвращает новый объект (большинство методов String делают это) или возвращает этот объект для целей объединения в цепочку или для повышения эффективности использования памяти).
Однажды я разработал трехмерный векторный объект, и для каждой математической операции у меня были реализованы оба метода. Для мгновенного метода шкалы:
источник
scale
(мутатор) иscaledBy
(генератор).Я вижу здесь несколько проблем, которые, я думаю, могут сбить с толку ... Ваша первая строка в вопросе:
Вы вызываете конструктор (new) и метод create ... Метод create () почти всегда будет статическим методом или методом построителя, и компилятор должен перехватить его в предупреждении или ошибке, чтобы вы знали, либо Кстати, этот синтаксис либо неверен, либо имеет несколько ужасных названий. Но позже вы не используете оба, так что давайте посмотрим на это.
Опять же, с созданием, но не с новым конструктором. Дело в том, я думаю, что вы ищете метод copy () вместо этого. Так что, если это так, и это просто плохое имя, давайте посмотрим на одну вещь ... вы называете mercedes.Paintedin (Color.Yellow) .Copy () - это должно быть легко посмотреть на это и сказать, что он «окрашен» перед копированием - просто нормальный поток логики, для меня. Поэтому поместите копию в первую очередь.
мне легко увидеть, что ты рисуешь копию, делая свою желтую машину.
источник
Первый подход имеет тот недостаток, который вы упомянули, но до тех пор, пока вы четко дадите понять в документах, у любого некомпетентного кодера не должно быть проблем. Весь код цепочки методов, с которым я лично работал, работал таким образом.
Второй подход, очевидно, имеет тот недостаток, что требует больше работы. Вы также должны решить, будут ли возвращаемые копии делать полные или глубокие копии: что лучше всего может варьироваться от класса к классу или от метода к методу, поэтому вы будете либо вносить несогласованность, либо идти на компромисс в отношении лучшего поведения. Стоит отметить, что это единственный вариант для неизменяемых объектов, таких как строки.
Что бы вы ни делали, не смешивайте и не сочетайте в одном классе!
источник
Я бы предпочел думать так же, как механизм «Методы расширения».
источник
Это вариант вышеуказанных методов. Различия в том, что в классе Car есть статические методы, которые соответствуют именам методов в Builder, поэтому вам не нужно явно создавать Builder:
Вы можете использовать те же имена методов, которые вы используете при вызовах связанного компоновщика:
Кроме того, в классе есть метод .copy (), который возвращает конструктор, заполненный всеми значениями из текущего экземпляра, поэтому вы можете создать вариант для темы:
Наконец, метод .build () компоновщика проверяет, все ли требуемые значения были предоставлены, и выдает их, если они отсутствуют. Может быть предпочтительнее требовать некоторые значения в конструкторе компоновщика и позволить остальным быть необязательными; в этом случае вы захотите один из шаблонов в других ответах.
источник