Есть ли смысл использовать компоновщики и текучие интерфейсы с инициализаторами объектов?

10

В Java и C # вы можете создать объект со свойствами, которые можно установить при инициализации, либо определив конструктор с параметрами, определив каждое свойство после создания объекта, либо используя шаблон интерфейса Builder / Fluid. Однако в C # 3 были введены инициализаторы объектов и коллекций, что означало, что шаблон компоновщика был практически бесполезен. На языке без инициализаторов можно реализовать конструктор, а затем использовать его так:

Vehicle v = new Vehicle.Builder()
                    .manufacturer("Toyota")
                    .model("Camry")
                    .year(1997)
                    .colour(CarColours.Red)
                    .addSpecialFeature(new Feature.CDPlayer())
                    .addSpecialFeature(new Feature.SeatWarmer(4))
                    .build();

И наоборот, в C # можно написать:

var vehicle = new Vehicle {
                Manufacturer = "Toyota",
                Model = "Camry",
                Year = 1997,
                Colour = CarColours.Red,
                SpecialFeatures = new List<SpecialFeature> {
                    new Feature.CDPlayer(),
                    new Feature.SeatWarmer { Seats = 4 }
                }
              }

... устранение необходимости в сборщике, как показано в предыдущем примере.

Основываясь на этих примерах, по-прежнему ли полезны компоновщики в C # или они полностью заменены инициализаторами?

svbnet
источник
are builders usefulпочему я продолжаю видеть подобные вопросы? Построитель - это шаблон проектирования. Конечно, он может быть представлен как языковая функция, но в конце концов это просто шаблон проектирования. Шаблон дизайна не может быть «неправильным». Это может или не может удовлетворить ваш вариант использования, но это не делает весь шаблон неправильным, только его применение. Помните, что в конце концов шаблоны проектирования решают конкретные проблемы - если вы не столкнулись с проблемой, то зачем вам пытаться ее решить?
ВЛАЗ
@vlaz Я не говорю, что сборщики не правы - фактически мой вопрос был в том, чтобы спросить, были ли какие-либо варианты использования для сборщиков, учитывая, что инициализаторы являются реализацией наиболее распространенного случая, когда вы использовали бы сборщики. Очевидно, что люди ответили, что конструктор все еще полезен для установки приватных полей, что, в свою очередь, ответило на мой вопрос.
svbnet
3
@vlaz Чтобы уточнить: я говорю, что инициализаторы в значительной степени заменили «традиционный» шаблон интерфейса конструктора / текучей среды - написание внутреннего класса компоновщика, а затем написание цепочечных методов сеттера внутри этого класса, которые создают новый экземпляр этого родительского класса. Я не говорю, что инициализаторы являются заменой для этого конкретного шаблона компоновщика; Я говорю, что инициализаторы избавляют разработчиков от необходимости реализовывать этот конкретный шаблон компоновщика, за исключением случаев использования, упомянутых в ответах, которые не делают шаблон компоновщика бесполезным.
svbnet
5
@vlaz Я не понимаю твоего беспокойства. «Вы говорили, что шаблон дизайна бесполезен» - нет, Джо спрашивал, полезен ли шаблон дизайна. Как это плохой, неправильный или ошибочный вопрос? Как вы думаете, ответ очевиден? Я не думаю, что ответ очевиден; это кажется хорошим вопросом для меня.
Таннер Светт
2
@vlaz, шаблоны синглтона и сервисного локатора неверны. Шаблоны дизайна Ergo могут быть неправильными.
Дэвид Арно

Ответы:

12

Как затронуло @ user248215, реальная проблема заключается в неизменности. Причина, по которой вы бы использовали компоновщик в C #, заключается в том, чтобы поддерживать явность инициализатора без необходимости выставления настраиваемых свойств. Это не вопрос инкапсуляции, поэтому я написал свой собственный ответ. Инкапсуляция является довольно ортогональной, так как вызов метода установки не подразумевает того, что фактически делает метод установки, или привязывает вас к его реализации.

В следующей версии C #, 8.0, вероятно, будет введено withключевое слово, которое позволит четко и кратко инициализировать неизменяемые объекты без необходимости написания компоновщиков.

Еще одна интересная вещь, которую вы можете сделать со сборщиками, в отличие от инициализаторов, заключается в том, что они могут приводить к различным типам объектов в зависимости от последовательности вызываемых методов.

Например

value.Match()
    .Case((DateTime d) => Console.WriteLine($"{d: yyyy-mm-dd}"))
    .Case((double d) => Console.WriteLine(Math.Round(d, 4));
    // void

var str = value.Match()
    .Case((DateTime d) => $"{d: yyyy-mm-dd}")
    .Case((double d) => Math.Round(d, 4).ToString())
    .ResultOrDefault(string.Empty);
    // string

Просто для пояснения приведенного выше примера, это библиотека сопоставления с образцом, которая использует шаблон построителя для создания «совпадения» путем указания случаев. Случаи добавляются путем вызова Caseметода, передающего ему функцию. Если valueприсваивается типу параметра функции, он вызывается. Вы можете найти полный исходный код на GitHub и, поскольку XML-комментарии трудно читать в виде простого текста, вот ссылка на встроенную документацию SandCastle (см. Раздел « Замечания »).

Алуан Хаддад
источник
Я не вижу, как это не могло быть сделано инициализатором объекта, используя IEnumerable<Func<T, TResult>>член.
Caleth
@Caleth Это было то, над чем я экспериментировал, но есть некоторые проблемы с этим подходом. Он не учитывает условные предложения (не показаны, но используются и продемонстрированы в связанной документации), а также не допускает вывод типа TResult. В конечном счете, вывод типа был во многом связан с этим. Я также хотел, чтобы он «выглядел» как структура управления. Также я не хотел использовать инициализатор, так как это позволило бы мутации.
Алуан Хаддад
12

Инициализаторы объекта требуют, чтобы свойства были доступны вызывающему коду. Вложенные строители могут получить доступ к частным членам класса.

Если вы хотите сделать Vehicleнеизменяемым (путем установки всех установщиков закрытыми), то для установки приватных переменных можно использовать вложенный конструктор.

user248215
источник
0

Все они служат разным целям !!!

Конструкторы могут инициализировать поля, которые отмечены, readonlyа также частные и защищенные элементы. Однако вы несколько ограничены в том, что вы можете делать внутри конструктора; thisНапример, вам следует избегать передачи на любые внешние методы и избегать вызова виртуальных членов, поскольку они могут выполняться в контексте производного класса, который еще не сконструирован. Кроме того, гарантируется, что конструкторы будут работать (если только вызывающая сторона не делает что-то очень необычное), поэтому, если вы задаете поля в конструкторе, код в остальной части класса может предположить, что эти поля не будут нулевыми.

Инициализаторы запускаются после построения. Они только позволяют вам называть публичной собственностью; частные и / или только для чтения поля не могут быть установлены. По соглашению, акт установки свойства должен быть довольно ограниченным, например, он должен быть идемпотентным и иметь ограниченные побочные эффекты.

Методы построителя являются реальными методами и поэтому допускают более одного аргумента, и по соглашению могут иметь побочные эффекты, включая создание объекта. Хотя они не могут устанавливать поля только для чтения, они могут делать что-то еще. Кроме того, методы могут быть реализованы как методы расширения (как почти все функциональные возможности LINQ).

Джон Ву
источник