Коли їхати вільно на C #?


78

Багато в чому мені дуже подобається ідея інтерфейсів Fluent, але з усіма сучасними особливостями C # (ініціалізатори, лямбдати, названі параметри) я вважаю себе думкою: «чи варто це?» Та «чи це правильна модель? використовувати? ". Чи міг би хто-небудь дати мені, як не прийняту практику, принаймні власний досвід чи матрицю рішення, коли використовувати Fluent шаблон?

Висновок:

Деякі хороші правила з цих відповідей:

  • Вільні інтерфейси значно допомагають, коли у вас більше дій, ніж сеттер, оскільки виклики отримують більше користі від контексту проходження.
  • Вільні інтерфейси слід розглядати як шар над верхнім верхом, а не єдиним засобом використання.
  • Сучасні функції, такі як лямбда, ініціалізатори та названі параметри, можуть працювати поруч, щоб зробити вільний інтерфейс ще більш дружним.

Ось приклад того, що я маю на увазі під сучасними функціями, що роблять його менш потрібним. Візьмемо для прикладу (можливо, поганий приклад) Вільний інтерфейс, який дозволяє мені створити Співробітника на зразок:

Employees.CreateNew().WithFirstName("Peter")
                     .WithLastName("Gibbons")
                     .WithManager()
                          .WithFirstName("Bill")
                          .WithLastName("Lumbergh")
                          .WithTitle("Manager")
                          .WithDepartment("Y2K");

Легко записати ініціалізатори, такі як:

Employees.Add(new Employee()
              {
                  FirstName = "Peter",
                  LastName = "Gibbons",
                  Manager = new Employee()
                            {
                                 FirstName = "Bill",
                                 LastName = "Lumbergh",
                                 Title = "Manager",
                                 Department = "Y2K"
                            }
              });

Я міг би також використовувати названі параметри в конструкторах у цьому прикладі.


1
Хороше запитання, але я думаю, що це більше питання про вікі
Іво,

Ваше запитання позначено "fluent-nhibernate". Отже, ви намагаєтеся вирішити, чи створювати вільний інтерфейс, чи використовувати поточну конфігурацію nhibernate vs. XML?
Ілля Коган

1
Проголосували за перехід на Programmers.SE
Метт Еллен

@Ilya Kogan, я думаю, що це фактично позначений "fluent-interface", який є загальним тегом для вільного інтерфейсу. Це питання не стосується нібернатату, а як ви вже сказали, чи створити вільний інтерфейс. Дякую.

1
Ця публікація надихнула мене на роздуми про спосіб використання цієї моделі у C. Мою спробу можна знайти на веб-сайті сестри Code Review .
otto

Відповіді:


28

Написання безперебійного інтерфейсу (я з цим заплутався ) вимагає більше зусиль, але це має окупність, тому що якщо ви зробите це правильно, наміри отриманого коду користувача є більш очевидним. Це по суті форма мовного домену.

Іншими словами, якщо ваш код читається набагато більше, ніж написано (а який код ні?), То вам слід розглянути можливість створення вільного інтерфейсу.

Вільні інтерфейси більше стосуються контексту, і це набагато більше, ніж просто способи налаштування об'єктів. Як ви можете бачити за посиланням вище, я використовував плавний API API для досягнення:

  1. Контекст (тому, коли ти зазвичай робиш багато дій у послідовності з однією і тією ж справою, ти можеш ланцюжок дій, не повідомляючи про свій контекст знову і знову).
  2. Виявлення (коли ви переходите до objectA.інтеліссенсу, дає вам багато підказок. У моєму випадку вище, plm.Led.ви надаєте всі варіанти управління вбудованим світлодіодом, а також plm.Network.дає змогу робити з мережевим інтерфейсом. plm.Network.X10.Надає підмножину мережеві дії для пристроїв X10. Ви не отримаєте цього за допомогою ініціалізаторів конструктора (якщо ви не хочете створювати об'єкт для кожного різного типу дій, що не є ідіоматичним).
  3. Рефлексія (не використовується в прикладі вище) - здатність приймати передане в виразі LINQ та маніпулювати ним є дуже потужним інструментом, особливо в деяких допоміжних API, створених для одиничних тестів. Я можу передати вираз власника властивості, створити цілу купу корисних виразів, компілювати та запустити їх, або навіть використовувати геть властивості для налаштування мого контексту.

Я зазвичай роблю:

test.Property(t => t.SomeProperty)
    .InitializedTo(string.Empty)
    .CantBeNull() // tries to set to null and Asserts ArgumentNullException
    .YaddaYadda();

Я не бачу, як ви також можете зробити щось подібне без вільного інтерфейсу.

Редагувати 2 : Ви також можете зробити цікаві покращення читабельності, наприклад:

test.ListProperty(t => t.MyList)
    .ShouldHave(18).Items()
    .AndThenAfter(t => testAddingItemToList(t))
    .ShouldHave(19).Items();

Дякую за відповідь, проте я знаю причину використовувати Fluent, але шукаю більш конкретну причину використовувати його над чимось на зразок мого нового прикладу вище.
Ендрю Ханлон

Дякуємо за розширену відповідь. Я думаю, ви окреслили два хороших правила: 1) Використовуйте Fluent, коли у вас є багато дзвінків, які виграють від контексту "проходження". 2) Подумайте про Fluent тоді, коли у вас більше дзвінків, ніж сеттер.
Ендрю Ханлон

2
@ach, я не бачу нічого у цій відповіді про "більше дзвінків, ніж сестер". Вас бентежить його твердження про те, що "код [що] читається набагато більше, ніж написано"? Це не про власників / сеттерів властивостей, а про те, щоб люди читали код порівняно з людьми, які писали код - про те, щоб людині було зручно читати код , оскільки ми зазвичай читаємо заданий рядок коду набагато частіше, ніж ми його модифікуємо.
Джо Вайт

@Joe White, я, можливо, перефразую термін "заклик" до "дії". Але ідея все ще стоїть. Дякую.
Ендрю Ханлон

Роздум про тестування - це зло!
Адроніус

24

Скотт Гензельман розповідає про це в епізоді 260 свого підкасту Hanselminutes з Джонатаном Картером. Вони пояснюють, що вільний інтерфейс більше схожий на інтерфейс інтерфейсу API. Ви не повинні надавати вільний інтерфейс як єдину точку доступу, а надати його як якийсь інтерфейс інтерфейсу коду на верхній частині "звичайного інтерфейсу API".

Джонатан Картер також розповідає про свій дизайн API у своєму блозі .


Велике спасибі за інформаційні посилання та інтерфейс інтерфейсу поверх API - це приємний спосіб його перегляду.
Ендрю Ханлон

14

Вільні інтерфейси - це дуже потужна функція, яка надається в контексті вашого коду, якщо не дотримуйтесь "правильних" міркувань.

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

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

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


Дякую за відповідь, ніколи не пізно дати добре продуману відповідь.
Ендрю Ханлон

Я не заперечую за префіксом "з". Це відрізняє їх від інших методів, які не повертають об'єкт для прикування. Наприклад , по position.withX(5)порівнянніposition.getDistanceToOrigin()
LegendLength

5

Початкова примітка: я беру питання з одним припущенням у питанні і роблю з цього свої конкретні висновки (наприкінці цієї публікації). Оскільки це, ймовірно, не дає повної, охоплюючої відповіді, я позначаю це як CW.

Employees.CreateNew().WithFirstName("Peter")…

Легко записати ініціалізатори, такі як:

Employees.Add(new Employee() { FirstName = "Peter",  });

На мої очі, ці дві версії повинні означати і робити різні речі.

  • На відміну від безмовної версії, вільна версія приховує той факт, що новий Employeeтакож Addредагується до колекції Employees- це лише говорить про те, що новий об’єкт - Created.

  • Сенс ….WithX(…)неоднозначний, особливо для людей, що походять з F #, у яких є withключове слово для виразів об'єктів : Вони можуть трактувати obj.WithX(x)як новий об'єкт, що виводиться з objцього, ідентичний, за objвинятком його Xвластивості, значення якої x. З другого боку, з другої версії зрозуміло, що жодні похідні об'єкти не створюються, а всі властивості визначені для вихідного об'єкта.

….WithManager().With
  • Це ….With…має ще одне значення: переключення "фокус" ініціалізації властивості на інший об'єкт. Той факт, що ваш вільний API має два різні значення, Withутруднює правильну інтерпретацію того, що відбувається ... саме тому, можливо, ви використали відступ у своєму прикладі, щоб продемонструвати намічене значення цього коду. Це було б зрозуміліше так:

    (employee).WithManager(Managers.CreateNew().WithFirstName("Bill").…)
    //                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    //                     value of the `Manager` property appears inside the parentheses,
    //                     like with `WithName`, where the `Name` is also inside the parentheses 

Висновки: "Приховування" досить простої мовної функції new T { X = x }, з вільним API ( Ts.CreateNew().WithX(x)), очевидно, можна зробити, але:

  1. Потрібно бути обережним, щоб читачі отриманого вільного коду все ж розуміли, що саме він робить. Тобто, вільний API повинен бути прозорим за змістом і однозначним. Розробка такого API може бути більшою роботою, ніж передбачалося (його, можливо, доведеться перевірити на зручність використання та прийняття) та / або ...

  2. його проектування може бути більше роботи, ніж потрібно: У цьому прикладі вільний API додає дуже мало «комфорту користувача» над базовим API (мовна функція). Можна сказати, що вільний API повинен зробити основу функції API / мови "простішою у використанні"; тобто це повинно заощадити програмісту чималу кількість зусиль. Якщо це просто інший спосіб написати те саме, це, мабуть, не варто, оскільки це не полегшує життя програміста, а лише ускладнює роботу дизайнера (див. Висновок №1 праворуч вище).

  3. Обидві точки вище мовчки припускають, що вільний API є шаром над існуючим API або мовою. Це припущення може бути ще одним хорошим керівництвом: Вільний API може бути додатковим способом зробити щось, а не єдиним способом. Тобто, може бути хорошою ідеєю запропонувати вільний API як вибір "відмови".


1
Дякую, що знайшли час, щоб додати моє запитання. Я визнаю, що мій обраний приклад був погано продуманий. У той час, коли я насправді хотів використати інтерфейс рідини для API запитів, який я розробляв. Я надто спрощено. Дякуємо за вказівку на недоліки та за хороші висновки.
Ендрю Ханлон

2

Мені подобається вільний стиль, він виражає наміри дуже чітко. Якщо у вас є приклад ініціалізатора об'єктів, який ви маєте після, вам потрібно встановити параметри загальнодоступних властивостей, щоб використовувати цей синтаксис. Скажімо, що у своєму прикладі ви не отримуєте великого завоювання над громадськими сетерами, тому що майже не ходили на стиль методу java-esque set / get.

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


Дякую за вашу відповідь, я вважаю, що ви добре висловились: «Вільно володіти багатьма селекторами».
Ендрю Ханлон

1

Я не був знайомий з терміном вільний інтерфейс , але це нагадує мені пару API, які я використовував, включаючи LINQ .

Особисто я не бачу, як сучасні функції C # заважали б корисності такого підходу. Я б сказав, що вони йдуть рука об руку. Наприклад, навіть простіше досягти такого інтерфейсу за допомогою методів розширення .

Можливо, поясніть свою відповідь конкретним прикладом того, як плавний інтерфейс можна замінити, використовуючи одну з сучасних функцій, про яку ви згадали.


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