Зарегистрируйтесь для доступа к 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);

Другой пример — это популярная библиотека для генерации различных данных @faker-js/faker. Она позволяет создать объект, который сохранит базовую конфигурацию:

import { Faker, ru } from '@faker-js/faker';

const faker = new Faker({ locale: ru });

console.log(`Привет, ${faker.person.fullName()}!`); // => Привет, Элеонора Авдеева!

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

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

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

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

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

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

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

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

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

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

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

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

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

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
от 6 300 ₽ в месяц
Разработка фронтенд-компонентов для веб-приложений
10 месяцев
с нуля
Старт 18 апреля
профессия
от 9 900 ₽ в месяц
Разработка фронтенд- и бэкенд-компонентов для веб-приложений
16 месяцев
с нуля
Старт 18 апреля
профессия
от 6 300 ₽ в месяц
Разработка бэкенд-компонентов для веб-приложений
10 месяцев
с нуля
Старт 18 апреля

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

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

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

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