Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Fluent interface JS: Коллекции

В программировании существует такое понятие, как паттерны проектирования. Это некоторые решения типичных задач, которые кто-то увидел, обобщил и придумал некий подход, подходящий для определенного типа задач. В этом уроке мы разберем Fluent Interface, который является одним из примеров таких паттернов.

Что такое 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

Теперь поработаем по 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 лежит thiscoll, только уже преобразованный. То есть мы получаем ссылку на то, что лежало в coll. И после этого мы продолжаем вызывать методы.

Несмотря на то, что where заменяет одну коллекцию на другую, он делает это в рамках одного объекта. В итоге получается, что мы мутируем объект каждый раз, когда вызываем такие методы.

Выводы

В этом уроке мы познакомились с паттерном проектирования Fluent Interface. Мы узнали, что в определении специалистов в области объектно-ориентированного программирования Fluent Interface — это способ реализации объектно-ориентированного API, который нацелен на повышение читабельности исходного кода программ.

Также узнали, что в функциональном программировании обычно используется функциональная композиция, что имеет сходство с Fluent Interface.

Еще нам удалось протестировать базовую функциональность нашей библиотеки и реализовать ее. Также мы изучили, как она устроена внутри.


Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты.

Для полного доступа к курсу нужен базовый план

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

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

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

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»