В чем смысл интерфейсов DSLs / fluent

недавно я смотрел веб-трансляцию о как создать свободно DSL

веб-трансляция представила класс изменения размера изображения, который позволяет указать входное изображение, изменить его размер и сохранить его в выходной файл, используя следующий синтаксис (используя C#):

Sizer sizer = new Sizer();
sizer.FromImage(inputImage)
     .ToLocation(outputImage)
     .ReduceByPercent(50)
     .OutputImageFormat(ImageFormat.Jpeg)
     .Save();

Я не понимаю, как это лучше, чем "обычный" способ это требует некоторых параметров:

sizer.ResizeImage(inputImage, outputImage, 0.5, ImageFormat.Jpeg);

С точки зрения удобства использования, это, кажется, намного проще в использовании, так как он ясно говорит вам, что метод ожидает в качестве входных данных. В отличие от интерфейса fluent, ничто не мешает вам опустить / забыть параметр / вызов метода, например:

sizer.ToLocation(outputImage).Save();

Итак, на мои вопросы:

1 - есть ли способ улучшить удобство использования интерфейса fluent (т. е. рассказать пользователю, что он должен делать)?

2 - является ли этот свободный интерфейс просто заменой для несуществующих именованных параметров метода в C#? Будут ли названные параметры делать свободные интерфейсы устаревшими, например, что-то подобное objective-C предлагает:

sizer.Resize(from:input, to:output, resizeBy:0.5, ..)

3 - свободно ли интерфейсы используются просто потому, что они в настоящее время популярны?

4 - или это был просто неудачный пример, который был выбран для трансляции? В таком случае, скажите мне в чем преимущества такого подхода, где имеет смысл его использовать.

кстати: я знаю о jquery и вижу, как легко это делает вещи, поэтому я не ищу комментариев об этом или других существующих примерах.

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

7 ответов


2 - это свободный подход к интерфейсу просто замена номера существующие параметры именованного метода в C#? Будут ли названные параметры делать fluent интерфейсы устарели, например, что-то похожие предложения objective-C:

Ну и да, и нет. Интерфейс fluent дает вам большую гибкость. То, что не может быть достигнуто с помощью именованных параметров:

sizer.FromImage(i)
 .ReduceByPercent(x)
 .Pixalize()
 .ReduceByPercent(x)
 .OutputImageFormat(ImageFormat.Jpeg)
 .ToLocation(o)
 .Save();

FromImage, ToLocation и OutputImageFormat в жидкости интерфейс, понюхай немного. Вместо этого я бы сделал что-то в этом роде, что, я думаю, намного яснее.

 new Sizer("bob.jpeg") 
 .ReduceByPercent(x)
 .Pixalize()
 .ReduceByPercent(x)
 .Save("file.jpeg",ImageFormat.Jpeg);

Fluent интерфейсы имеют те же проблемы, что и многие методы программирования, они могут быть использованы неправильно, чрезмерно или недостаточно. Я думаю, что когда этот метод используется эффективно, он может создать более богатую и более краткую модель программирования. Даже StringBuilder поддерживает его.

var sb = new StringBuilder(); 
sb.AppendLine("Hello")
 .AppendLine("World"); 

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

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

ваш пример кажется перебор.

в последнее время я сделал некоторый свободный интерфейс поверх SplitterContainer из Windows Forms. Возможно, семантическая модель иерархии элементов управления несколько сложна для правильного построения. Предоставляя небольшой fluent API, разработчик теперь может декларативно выразить, как должен работать его SplitterContainer. Использование идет как

var s = new SplitBoxSetup();
s.AddVerticalSplit()
 .PanelOne().PlaceControl(()=> new Label())
 .PanelTwo()
 .AddHorizontalSplit()
 .PanelOne().PlaceControl(()=> new Label())
 .PanelTwo().PlaceControl(()=> new Panel());
form.Controls.Add(s.TopControl);

теперь я сократил сложную механику иерархии управления до нескольких глаголов, которые имеют отношение к рассматриваемой проблеме.

надеюсь, что это помогает


считаем:

sizer.ResizeImage(inputImage, outputImage, 0.5, ImageFormat.Jpeg);

Что делать, если вы использовали менее понятные имена переменных:

sizer.ResizeImage(i, o, x, ImageFormat.Jpeg);

представьте, что вы распечатали этот код. Труднее определить, что это за аргументы, так как у вас нет доступа к сигнатуре метода.

с беглым интерфейсом, это яснее:

 sizer.FromImage(i)
 .ToLocation(o)
 .ReduceByPercent(x)
 .OutputImageFormat(ImageFormat.Jpeg)
 .Save();

кроме того, порядок, методы не важны. Это эквивалентно:

 sizer.FromImage(i)
 .ReduceByPercent(x)
 .OutputImageFormat(ImageFormat.Jpeg)
 .ToLocation(o)
 .Save();

кроме того, возможно, у вас могут быть значения по умолчанию для выходного изображения формат, и сокращение, так что это может стать:

 sizer.FromImage(i)
 .ToLocation(o)
 .Save();

это потребует перегруженных конструкторов для достижения того же эффекта.


Это один из способов реализации вещи.

для объектов, которые ничего не делают, но манипулируют одним и тем же элементом снова и снова, в этом нет ничего плохого. Рассмотрим потоки C++: они являются конечными в этом интерфейсе. Каждая операция возвращает поток снова, так что вы можете связать вместе другую операцию потока.

Если вы делаете LINQ и снова и снова манипулируете объектом, это имеет смысл.

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

var obj1 = object.Shrink(0.50); // obj1 is now 50% of obj2
var obj2 = object.Shrink(0.75); // is ojb2 now 75% of ojb1 or is it 75% of the original?

Если obj2 был 75% исходного объекта, то это означает, что вы делаете полную копию объекта каждый раз (и имеет свои преимущества во многих случаях, например, если вы пытаетесь сделать два экземпляра одной и той же вещи, но немного по-разному).

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

Не все классы работают так, и нет смысла делать такой дизайн. Например, этот стиль дизайна практически бесполезен при разработке аппаратного драйвера или ядра приложения GUI. Пока дизайн не включает ничего, кроме манипулирования некоторыми данными, этот шаблон не является плохим.


следует читать Domain Driven Design Эрик Эванс, чтобы получить некоторое представление, почему DSL считается хорошим выбором дизайна.

книга полна хороших примеров, советов по лучшей практике и шаблонов дизайна. Настоятельно рекомендуемый.


можно использовать вариацию на Fluent интерфейсе для принудительного применения определенных комбинаций необязательных параметров (например, требуется, чтобы присутствовал хотя бы один параметр из группы, и требуется, чтобы, если определенный параметр указан, некоторый другой параметр должен быть опущен). Например, можно предоставить функциональность, аналогичную Enumerable.Диапазон, но с синтаксисом, таким как IntRange.Из(5).До (19) или IntRange.Из(5).Меньше, чем(10).Stepby (2) или IntRange(3).Граф(19).Шаг(17). Применение во время компиляции чрезмерно сложных требований к параметрам может потребовать определения раздражающего числа структур или классов промежуточных значений, но в некоторых случаях этот подход может оказаться полезным в более простых случаях.


дальше @sam-saffronпредложение относительно гибкости свободного интерфейса при добавлении новой операции:

Если нам нужно добавить новую операцию, такую как Pixalize (), то в сценарии "метод с несколькими параметрами" для этого потребуется добавить новый параметр в сигнатуру метода. Это может потребовать изменения каждого вызова этого метода по всей кодовой базе, чтобы добавить значение для этого нового параметра (если только используемый язык допускает необязательный параметр).

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