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

Конфигурация JS: Объектно-ориентированный дизайн

Markdown — упрощённый язык разметки, который удобен при работе с текстом (в отличие от HTML). Браузеры не умеют отображать Markdown напрямую, поэтому он транслируется в HTML и уже затем показывается. Трансляция Markdown в HTML описывается чистой функцией. Она не зависит от внешнего окружения, детерминирована и не порождает побочных эффектов.

html = markdownToHtml(markdown);

На входе текст (в формате Markdown), на выходе — тоже текст (в формате HTML). Если нужно изменить поведение трансляции, то достаточно передать вторым параметром массив опций.

// Вторым параметром передаём опции трансляции
// `sanitize` — флаг, отвечающий за включение безопасного рендеринга.
// Если его выключить, то теги `<script>` вставленные в Markdown отобразятся как есть.
const html = markdownToHtml(markdown, { sanitize: false });

Теперь давайте вообразим объектно-ориентированную версию этого кода. Перед тем, как двигаться дальше, попробуйте отвлечься от чтения и подумайте над следующими вопросами:

  • Что мы вообще хотим получить такого от ООП, чего не даёт нам чистая функция?
  • Как будет выглядеть получившийся интерфейс?

Как вы помните, классы позволяют реализовать абстракцию. Можно ли сказать, что в процессе преобразования Markdown в HTML есть абстракция? Нет. Абстракция подразумевает наличие некоторого понятия (типа), значения которого обладают временем жизни. Это значит, что она создается и затем многократно и по-разному используется. Например, невозможно представить работу с пользователем в виде одной функции. Если говорить о Markdown, то конкретный текст этого формата не интересует нас сам по себе, мы не определяем над ним некоторый набор операций и не собираемся им активно пользоваться. Все, что мы хотим, прямо здесь и сейчас (в том коде) - получить HTML и забыть про Markdown.

Если бы мы хотели построить вокруг текста абстракцию, то код выглядел бы так:

// Объект md описывает собой переданный текст markdown и позволяет им манипулировать
const md = new Markdown(markdown);
const html = md.render();

В примере выше тип Markdown представляет собой абстракцию над текстом в формате Markdown. Смысла в таком коде мало, а вот проблем он доставит. Эти две строчки начнут неразрывно встречаться в каждом месте, в котором требуется получить HTML. Объект md становится сразу не нужен, как только получен HTML, у него нет времени жизни. Такой антипаттерн особенно часто встречается у новичков. Загвоздка здесь именно в том, чтобы разобраться, где у нас абстракция данных, а где нет.

// Типичный избыточный код в том месте, где абстракцию сделали, но она не нужна
const md1 = new Markdown(markdown1);
const html1 = md1.render();

// Еще раз для закрепления
const md2 = new Markdown(markdown2);
const html2 = md2.render();

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

const md = new Markdown();
// очень важно, чтобы render оставался чистой функцией и не сохранял markdown внутри объекта
const html1 = md.render(markdown1);
const html2 = md.render(markdown2);

В этом коде класс Markdown — тип, относящийся к транслятору, а не к тексту. У такого объекта жизненный цикл шире, чем ожидание однократного вызова функции render() (как в предыдущем случае). Он может (и должен) переиспользоваться столько раз, сколько потребуется. Для этого важно оставить функцию render() чистой и не менять состояние объекта между вызовами.

Тогда становится непонятно, зачем здесь вообще объект. И на это есть 2 причины.

  1. Полиморфизм подтипов. Разберём в последующих курсах.
  2. Вторая и главная причина (для данного случая) — Конфигурация.

Разберём последний пункт подробнее. Представьте что Markdown на проекте используется повсеместно (на Хекслете очень часто) и код генерации HTML выглядит так:

// В одном месте
const html1 = markdownToHtml(markdown1, { sanitize: true });

// Где-то в другом месте
const html2 = markdownToHtml(markdown2, { sanitize: true });

Чем больше возникает таких мест, тем больше дублируется передача опций. Изменение поведения потребует переписывания всех мест вызова этой функции. Логичным шагом было бы задать опции в одном месте и затем их переиспользовать.

// В одном месте
const html1 = markdownToHtml(markdown1, options);

// Где-то в другом месте
const html2 = markdownToHtml(markdown2, options);

Использование объекта позволяет убрать явную передачу (про которую легко забыть). Суть этого паттерна заключается в конфигурировании. То есть объект в данном случае выступает в роли контейнера, содержащего опции для Markdown, которые применяются при рендеринге, что позволяет их не передавать каждый раз.

const md = new Markdown({ sanitize: true });
const html1 = md.render(markdown1);
const html2 = md.render(markdown2);

Под конфигурированием всегда понимается передача опций (различных настроек, необходимых данной библиотеке) в конструктор во время создания объекта. Особенно полезной такая конфигурация становится тогда, когда объект создаётся в одном месте программы (на этапе инициализации приложения), а используется в других местах. Возможность конфигурации не навязывает саму конфигурацию. Как правило, подобные объекты можно создавать и без указания чего-либо, тогда поведение остаётся "дефолтным", но смысл от этого не меняется.

const md = new Markdown();
const html = md.render(markdown);

Популярная библиотека для HTTP-запросов axios построена по такому же принципу. Она позволяет создать объект, который сохранит базовую конфигурацию.

Попробуйте проверить себя. Выполнение HTTP-запроса это абстракция данных или нет?

import axios from 'axios';

const client = new axios.Axios({ timeout: 1000 });
const response = await client.get('https://ru.hexlet.io/lessons.rss');
console.log(response.status);

Данный приём не является прерогативой классов и объектов. В функциональных языках (и в JS) он крайне просто реализуется через замыкание


Дополнительные материалы

  1. Библиотека MarkdownIt

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

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

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

Ошибки, сложный материал, вопросы >
Нашли опечатку или неточность?

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

Что-то не получается или материал кажется сложным?

Загляните в раздел «Обсуждение»:

  • задайте вопрос. Вы быстрее справитесь с трудностями и прокачаете навык постановки правильных вопросов, что пригодится и в учёбе, и в работе программистом;
  • расскажите о своих впечатлениях. Если курс слишком сложный, подробный отзыв поможет нам сделать его лучше;
  • изучите вопросы других учеников и ответы на них. Это база знаний, которой можно и нужно пользоваться.

Об обучении на Хекслете

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

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

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

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

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

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

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

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы

С нуля до разработчика. Возвращаем деньги, если не удалось найти работу.

Иконка программы Фронтенд-разработчик
Профессия
Разработка фронтенд-компонентов веб-приложений
25 мая 10 месяцев
Иконка программы Node.js-разработчик
Профессия
Разработка бэкенд-компонентов веб-приложений
25 мая 10 месяцев
Иконка программы Fullstack-разработчик
Профессия
Новый
Разработка фронтенд и бэкенд компонентов веб-приложений
25 мая 16 месяцев

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

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

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

Даю согласие на обработку персональных данных, соглашаюсь с «Политикой конфиденциальности» и «Условиями оказания услуг»