Разработка потокового интерфейса (Fluent API)

Fluent API это довольно интересный способ предоставить другим разработчикам доступ к функциональности класса. Например при использовании точки в LINQ означает использование потокового АПИ. Их не только легко и просто использовать, но и также просто создать.

Впервые потоковый интерфейс был предложен Eric Evans и Martin Fowler для реализации, направленный на повышение читабельности кода.

Пример потокового API:

var mpg = new Car(make : “audi”, model: “a4”)
 .WithHorsePower(250)
 .WithFuel(gallons:10)
 .WithWeight(tons:3)
 .Mpg();

Для сравнения стандартный паттерн создания объектов:

var car = new Car(make: “audi”, model: “a4”) {
    HorsePower = 250,
    Fuel = 10,
    Weight = 3
}

var mpg = car.Mpg();

Как видно, оба способа позволяют довольно красиво создать объект и выполнить какое-либо действие. Возникает вопрос: зачем использовать потоковый интерфейс?

Повышается читабельность исходного кода программы. Такой подход позволяет создать модификаторы объектов, без изменения основного объекта (именно так работает string), при каждом изменении строки создается новый объект с новым значением. Такой подход позволяет настроить фильтрацию объектов в коллекции, именно так работает LINQ, используя:

List<Product> products= ProductsCollection.Where(x => x.CategoryID == 8).ToList();

получаем фильтрацию товаров по категории и только в конце, используя метод ToList действительно ее применяем и получаем список. До вызова ToList результатом метода будет IQueryable<Product>.

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

Итак, из чего состоит потоковый интерфейс.

Конструктор

Каждый потоковый интерфейс должен содержать конструкор.

new Car(make : “audi”, model:“a4”) // entry point

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

Цепочки методов

После создания конструктора начинается самое интересное: цепочки методов. Они позволяют реализовывать довольно сложные модели. Самое простое это определение в объекте дополнительных свойств.

.WithHorsePower(250)
.WithFuel(gallons:10)
.WithWeight(tons:3)

В предложенном выше коде, не происходит ничего магического, однако это и есть потоковый интерфес.

Исполнитель

Это наверно одна из самых важных частей интерфейса (с аналогом ToList()). Метод исполнитель позволяет выйти из цепочки методов и получить результат. 

Пример

В качестве примера рассмотрим немного придуманный, но все же возможный объект Машина. Для начала нам понадобится класс Автомобиль. Сначала определим его конструктор.

public class Car
{
    private readonly string _make;
    private readonly string _model;

public Car(string make, string model)
{
    _make = make;
    _model = model;
}

}

Добавим возможные цепочки методов:

    public Car WithHorsePower(int bhp)
    {
        _bhp = bhp;
        return this;
    }

public Car WithFuel(int gallons)
{
    _gallons = gallons;
    return this;
}

public Car WithWeight(int tons)
{
    _tons = tons;
    return this;
}</pre>

В этом заключается вся магия. Возвращаем объект Car и можем снова использовать публичные методы потокового интерфейса. LINQ в свою очередь возвращает объект типа IQueryable<T>. Метод исполнитель определим последним:

    public int Mpg()
    {
        // please don’t use this for real mpg
        return Gallons/Math.Max(Tons, 1);
    }

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

Заключение

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

Кстати, потоковый интерфес хорошо используется в валидаторах и IoC, например в Structure Map:

// Initialize the static ObjectFactory container
    ObjectFactory.Initialize(x =>
    {
        x.ForRequestedType<IValidator>().TheDefaultIsConcreteType<Validator>();
    });

Ссылки

Комментарии

comments powered by Disqus