В программировании существует такое понятие, как паттерны проектирования. Это некоторые решения типичных задач, которые кто-то увидел, обобщил и придумал некий подход, подходящий для определенного типа задач. В этом уроке мы разберем Fluent Interface, который является одним из примеров таких паттернов.
Для начала посмотрим, как выглядит наш DSL, и узнаем, что в нем является Fluent Interface:
const cars [
{ brand: 'bmw', model: 'm5', year: 2014 },
{ brand: 'bmw', model: 'm4', year: 2013 },
{ brand: 'kia', model: 'sorento', year: 2014 },
{ brand: 'kia', model: 'rio', year: 2010 },
];
// Создаем некоторую коллекцию и оборачиваем в класс Enumerable наш cars
const coll = new Enumerable(cars);
// Применяем различные методы, которые производят некоторые изменения с машинами
// Делаем сортировку
const result = coll.orderBy((car) => car.year)
// Делаем фильтрацию
.where((car) => car.brand === 'kia')
// Делаем выборку определенных полей изнутри
.select((car) => car.model)
// Получаем новый массив, в котором видим, что выбирали и как
.toArray();
// ['rio', 'sorento' ]
Fluent Interface здесь заключается в том, что мы постоянно через точку продолжаем вызовы. В нашем примере идет вызов метода, а после этого точка, и это повторяется. Эти элементы кода могут вызываться бесконечно друг за другом, но бывают методы, которые являются окончательными.
Например, toArray
возвращает конкретный массив, и после него уже нельзя вызвать wear
. Поэтому методы бывают разные. Некоторые могут продолжать этот интерфейс, некоторые не могут.
В определении специалистов в области объектно-ориентированного программирования Fluent Interface — это способ реализации объектно-ориентированного API, который нацелен на повышение читабельности исходного кода программ.
Это не является прерогативой объектно-ориентированного программирования. Хотя из-за того, как вызываются методы, этот подход здесь выглядит именно так.
В функциональном программировании обычно используется чуть более мощная стратегия, которая называется функциональная композиция. В таком случае код остается неизменяемым, у нас будут чистые функции, и при этом она позволяет делать чуть более крутые вещи. Но это выходит за рамки этого урока. Главное понимать, что функциональная композиция и fluent interface имеют сходство.
Ниже приведен код на jQuery — это пример популярного DSL, который позволяет читабельно описывать то, что мы хотим сделать со страницей:
$(this).find('img').stop()
.animate({ opacity: 0.8 }, 300).end()
.find('.viewcasestudy').fadeIn('fast');
Теперь поработаем по TDD. В данном случае библиотека простая, а код предсказуемый и детерминированный, поэтому все очень просто тестировать и писать:
// Создаем тестовые данные
const cars = [
{ brand: 'bmw', model: 'm5', year: 2014 },
{ brand: 'bmw', model: 'm4', year: 2013 },
{ brand: 'kia', model: 'sorento', year: 2014 },
{ brand: 'kia', model: 'rio', year: 2010 },
{ brand: 'kia', model: 'sportage', year: 2012 },
];
// Делаем оборачивание коллекции машин
coll = new Enumerable(cars);
// Делаем выборку
const result = coll
.where((car) => car.brand === 'kia')
.where((car) => car.year > 2011);
assert.deepEqual(result.toArray(), [cars[2], cars[4]]);
В конце мы используем assert
, но уже используем не equal
, а deepEqual
. Этот метод тоже проверяет равенство. В следующих уроках мы поймем, зачем это нужно.
Мы проверяем, что результирующий массив равен массиву [cars[2], cars[4]]
. То есть машины под индексами 2 и 4 соответствуют параметрам выборки.
Это весь тест, который необходим, чтобы протестировать базовую функциональность нашей библиотеки. Осталось реализовать ее и посмотреть, как она устроена внутри.
Реализовываем библиотеку:
// Определяем класс
class Enumerable {
// Конструктор, который устанавливает коллекцию внутрь
constructor(collection) {
this.collection = collection;
}
where(fn) {
// Присваиваем новую коллекцию — полученную фильтрацию предыдущей
// По сути одну коллекцию заменяем на другую, пропуская через фильтр
this.collection = this.collection.filter(fn);
return this;
}
toArray() {
return this.collection;
}
}
На выходе из функции where
мы производим действие, которое дает возможность использовать Fluent interface. Мы делаем return this
.
this
— это объект, с которым мы работаем в данный момент. Поэтому на выходе у нас получается тот же объект, из которого можно дальше вызывать эти же методы до тех пор, пока один из них не перестанет возвращать this
. Это всё, что нужно для Fluent interface.
В конце реализации стоит toArray()
, который возвращает саму коллекцию. Больше никаких преобразований здесь нет. Поэтому реализация Fluent interface — очень простая.
Для большего понимания этого, разложим еще один пример:
const result = coll.orderBy((car) => car.year);
result.where((car) => car.brand === 'kia');
result.select((car) => car.model);
result.toArray();
Допустим, мы берем коллекцию и делаем orderBy
. Она возвращает this
— это та же коллекция. Получается, что в result
лежит this
— coll
, только уже преобразованный. То есть мы получаем ссылку на то, что лежало в coll
. И после этого мы продолжаем вызывать методы.
Несмотря на то, что where
заменяет одну коллекцию на другую, он делает это в рамках одного объекта. В итоге получается, что мы мутируем объект каждый раз, когда вызываем такие методы.
В этом уроке мы познакомились с паттерном проектирования Fluent Interface. Мы узнали, что в определении специалистов в области объектно-ориентированного программирования Fluent Interface — это способ реализации объектно-ориентированного API, который нацелен на повышение читабельности исходного кода программ.
Также узнали, что в функциональном программировании обычно используется функциональная композиция, что имеет сходство с Fluent Interface.
Еще нам удалось протестировать базовую функциональность нашей библиотеки и реализовать ее. Также мы изучили, как она устроена внутри.
Вам ответят команда поддержки Хекслета или другие студенты.
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
Наши выпускники работают в компаниях:
Зарегистрируйтесь или войдите в свой аккаунт