Когда свободно говорить на C #?

78

Во многих отношениях мне действительно нравится идея интерфейсов Fluent, но со всеми современными функциями C # (инициализаторы, лямбды, именованные параметры) я думаю, «стоит ли это того?» И «Правильный ли это шаблон для использовать?». Может ли кто-нибудь дать мне, если не принятую практику, хотя бы свой собственный опыт или матрицу решений о том, когда использовать шаблон Fluent?

Заключение:

Некоторые хорошие правила из ответов на данный момент:

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

Вот пример того, что я имею в виду под современными функциями, заставляющими его чувствовать себя менее нужным. Возьмем, к примеру, (возможно, плохой пример) интерфейс 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
Хороший вопрос, но я думаю, что это больше вопрос вики
Ivo
Ваш вопрос помечен как "свободный-nhibernate". Итак, вы пытаетесь решить, создавать ли свободный интерфейс или использовать свободную конфигурацию nhibernate против XML?
Илья Коган
1
Проголосовал за переход на Programmers.SE
Мэтт Эллен
@ Илья Коган, я думаю, что на самом деле это тег "свободный интерфейс", который является общим тегом для интерфейса беглого интерфейса. Этот вопрос относится не к nhibernate, а к тому, что вы сказали только о том, создавать ли свободный интерфейс. Благодарю.
1
Этот пост вдохновил меня подумать о том, как использовать этот шаблон в C. Мою попытку можно найти на родственном сайте Code Review .
Отто

Ответы:

28

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

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

Свободные интерфейсы - это больше контекст, и это гораздо больше, чем просто способы настройки объектов. Как вы можете видеть из приведенной выше ссылки, я использовал API-интерфейс fluent-ish для достижения следующих целей:

  1. Контекст (поэтому, когда вы, как правило, выполняете много действий в одной и той же последовательности, вы можете связать действия в цепочку, не объявляя контекст снова и снова).
  2. Обнаружение (когда вы переходите к objectA.intellisense, вы получаете много подсказок. В моем случае выше, 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) Используйте Свободно, когда у вас много вызовов, которые выигрывают от «сквозного» контекста. 2) Подумайте о свободном разговоре, когда у вас больше вызовов, чем сеттеров.
Эндрю Хэнлон,
2
@ach, я не вижу ничего в этом ответе о "больше звонков, чем сеттеров". Вас смущает его заявление о том, что «код [это] читается намного больше, чем написано»? Дело не в методах получения / установки свойств, а в том, что люди читают код, а люди пишут код - в том, чтобы облегчить чтение кода для людей , потому что мы обычно читаем определенную строку кода гораздо чаще, чем модифицируем ее.
Джо Уайт
@ Джо Уайт, возможно, мне следует перефразировать мой термин «призыв» к «действию». Но идея все еще стоит. Благодарю.
Эндрю Хэнлон,
Отражение для тестирования это зло!
Адрониус
24

Скотт Хансельман рассказывает об этом в эпизоде ​​260 своего подкаста Hanselminutes с Джонатаном Картером. Они объясняют, что свободный интерфейс больше похож на пользовательский интерфейс API. Вы не должны предоставлять свободный интерфейс в качестве единственной точки доступа, а должны предоставлять ее как своего рода кодовый интерфейс поверх «обычного интерфейса API».

Джонатан Картер также немного рассказывает о дизайне API в своем блоге .

Кристоф Клас
источник
Большое спасибо за информационные ссылки и пользовательский интерфейс поверх API - хороший способ посмотреть на это.
Эндрю Хэнлон,
14

Свободные интерфейсы - это очень мощные функции, которые могут быть предоставлены в контексте вашего кода при «правильных» рассуждениях.

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

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

Ключ в том, чтобы думать о свободном синтаксисе как о конкретной реализации языка, специфичного для предметной области. В качестве действительно хорошего примера того, о чем я говорю, взгляните на StoryQ, который использует беглость как средство выражения DSL очень ценным и гибким способом.

S.Robins
источник
Спасибо за ответ, никогда не поздно дать хорошо продуманный ответ.
Эндрю Хэнлон
Я не против префикса «с» для методов. Это отличает их от других методов, которые не возвращают объект для создания цепочки. Например , по 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
Спасибо за ответ - я добавил базовый пример, чтобы помочь прояснить мой вопрос.
Эндрю Хэнлон,