Чи є сенс використовувати будівельники та інтерфейси флюїду з ініціалізаторами об'єктів?


10

У Java та C # ви можете створити об’єкт із властивостями, які можна встановити при ініціалізації, або визначивши конструктор з параметрами, визначивши кожну властивість після побудови об'єкта, або використовуючи шаблон інтерфейсу будівельника та флюїду. Однак 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 #, або їх повністю витіснили ініціалізатори?


are builders usefulчому я все ще бачу такі запитання? Конструктор - це модель дизайну - звичайно, він може бути викритий як мовна особливість, але наприкінці дня це лише шаблон дизайну. Шаблон дизайну не може бути "неправильним". Він може або не може виконати ваш випадок використання, але це не зробить всю схему помилковою, просто її застосування. Пам'ятайте, що в кінці дня схеми дизайну вирішують конкретні проблеми - якщо ви не стикаєтеся з проблемою, то чому б ви намагалися її вирішити?
ВЛАЗ

@vlaz Я не кажу, що будівельники помиляються - насправді моє запитання запитувало, чи існують випадки використання для будівельників, які дають ініціалізатори - це реалізація найбільш поширеного випадку, коли ви б використовували будівельників. Очевидно, люди відповіли, що будівельник все ще корисний для встановлення приватних полів, що, в свою чергу, відповіло на моє запитання.
svbnet

3
@vlaz Щоб уточнити: я кажу, що ініціалізатори значною мірою замінили "традиційний" шаблон інтерфейсу для побудови / текучого середовища написання внутрішнього класу builder, а потім записують ланцюгові методи встановлення всередині цього класу, які створюють новий екземпляр цього батьківського класу. Я не кажу, що ініціалізатори - це заміна цього конкретного шаблону для будівельників; Я кажу, що ініціалізатори економлять розробників, що мають реалізувати певний шаблон конструктора, окрім випадків використання, згаданих у відповідях, що не робить модель будівельника марною.
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 Насправді я щось експериментував, але з цим підходом є деякі проблеми. Він не допускає умовних пропозицій (не показані, але використані та продемонстровані у зв'язаній документації) і не дозволяє виводити тип TResult. Врешті-решт, висновок типу мав багато спільного з цим. Я також хотів, щоб це "виглядало" як контрольна структура. Також я не хотів використовувати ініціалізатор, оскільки це дозволило б мутувати.
Алуан Хаддад

12

Ініціалізатори об'єктів вимагають, щоб властивості повинні бути доступними кодом виклику. Вкладені будівельники можуть отримати доступ до приватних членів класу.

Якщо ви хотіли зробити Vehicleнепорушними (шляхом зробити всі сетери приватними), тоді вкладений конструктор може бути використаний для встановлення приватних змінних.


0

Всі вони служать різним цілям !!!

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

Ініціалізатори працюють після будівництва. Вони дозволяють вам називати загальнодоступні об'єкти; приватні та / або лише для читання поля не можна встановлювати. Згідно з умовою, акт встановлення властивості повинен бути досить обмеженим, наприклад, він повинен бути безсильним і мати обмежені побічні ефекти.

Методи Builder - це фактичні методи, тому дозволяють використовувати більше одного аргументу, і за умовою можуть мати побічні ефекти, включаючи створення об'єктів. Хоча вони не можуть встановити поля лише для читання, вони можуть зробити все інше. Також методи можуть бути реалізовані як методи розширення (як і майже всі функції LINQ).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.