Зарегистрируйтесь, чтобы продолжить обучение

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

В объектно-ориентированном программировании одна из ключевых проблем — это управление конфигурацией. Допустим, у вас есть функция, которая выполняет задачу и принимает различные параметры или опции. Если эта функция вызывается в нескольких местах приложения, то может возникнуть необходимость изменить ее поведение. Именно эту проблему мы разберем в данном уроке.

Трансляция Markdown в HTML

Рассмотрим простую функцию, которая переводит текст в формате Markdown в HTML.

Markdown — упрощенный язык разметки, который удобен при работе с текстом в отличие от HTML. Браузеры не умеют отображать Markdown напрямую, поэтому он транслируется в HTML и уже затем показывается.

Функция перевода Markdown в HTML не зависит от внешнего окружения, детерминирована и не создает побочных эффектов. На входе текст в формате Markdown, на выходе — тоже текст, но в формате HTML. Если нужно изменить поведение трансляции, то достаточно передать дополнительные опции.

Вот пример такой функции, которая преобразует текст из формата Markdown в HTML:

from markdown import markdown

html = markdown(markdown_text)

В этом примере кода мы используем функцию markdown из библиотеки markdown, чтобы преобразовать текст в формате Markdown в HTML.

Если мы хотим настроить поведение трансляции, мы можем передать дополнительные опции функции:

html = markdown(markdown_text, output_format="html5")

Здесь мы передаем дополнительный параметр output_format в функцию markdown, чтобы изменить формат вывода.

Теперь представим, что было бы, если бы мы решили применить объектно-ориентированный подход к этой проблеме.

Применение ООП

Перед тем, как двигаться дальше, подумаем над следующими вопросами:

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

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

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

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

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

class MarkdownToHTML:
    def __init__(self, markdown_text):
        self.markdown_text = markdown_text

    def render(self):
        return markdown(self.markdown_text)

md = MarkdownToHTML(markdown_text)
html = md.render()

В этом примере мы создали класс MarkdownToHTML, который принимает текст в формате Markdown и имеет метод render(), транслирующий этот текст в HTML.

Использование объектов может привести к проблемам, когда код становится избыточным:

md1 = MarkdownToHTML(markdown1)
html1 = md1.render()

md2 = MarkdownToHTML(markdown2)
html2 = md2.render()

Здесь нам приходится для каждого блока Markdown, который мы хотим преобразовать в HTML, создавать новый объект MarkdownToHTML. Это может быть избыточно и приведет к дублированию кода.

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

md = MarkdownToHTML()
# Важно, чтобы render оставался чистой функцией и не сохранял markdown внутри объекта
html1 = md.render(markdown1)
html2 = md.render(markdown2)

Здесь мы преобразовали класс MarkdownToHTML так, чтобы он представлял собой абстракцию не над текстом Markdown, а над транслятором Markdown в HTML. Это значит, что жизненный цикл такого объекта стал шире, чем ожидание однократного вызова функции render().

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

Стоит отметить, что мы столкнулись здесь с двумя важными концепциями:

  • Полиморфизм подтипов, который мы рассмотрим в последующих курсах
  • Конфигурация, которая является ключевым моментом для данного случая

Представим, что в вашем проекте Markdown используется повсеместно, и код для генерации HTML в разных местах выглядит примерно так:

# В одном месте
html1 = markdown_to_html(markdown1, sanitize=True)

# В другом месте
html2 = markdown_to_html(markdown2, sanitize=True)

При таком подходе в каждом месте вызова функции markdown_to_html мы дублируем передачу параметра sanitize. Если бы нам потребовалось изменить поведение этой функции, мы были бы вынуждены переписать все места, где она вызывается. Более логичным было бы задать параметры один раз и затем их переиспользовать.

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

options = {'sanitize': True}

md = MarkdownToHTML(**options)
html1 = md.render(markdown1)
html2 = md.render(markdown2)

Здесь мы создали объект класса MarkdownToHTML, передав в его конструктор параметр sanitize. Этот объект затем можно использовать для преобразования текста Markdown в HTML с заданными параметрами.

Под конфигурированием мы понимаем передачу опций — различных настроек, необходимых данной библиотеке — в конструктор во время создания объекта. Эта возможность особенно полезна, когда объект создается в одном месте программы, например, на этапе инициализации приложения, а используется в других местах.

Возможность конфигурации не обязывает ее использовать. Мы всегда можем создать объект и без указания каких-либо параметров. В таком случае его поведение будет по умолчанию:

md = MarkdownToHTML()
html = md.render(markdown)

Так использование объектов вместо функций дает нам больше гибкости и контроля над настройками и поведением кода.

Выводы

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


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

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

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

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

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

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

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

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

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

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

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
Программирование на Python, Разработка веб-приложений и сервисов используя Django, проектирование и реализация REST API
10 месяцев
с нуля
Старт 26 декабря

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

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

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

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