Многие объекты в ООП не являются абстракцией данных, а используются как способ сохранить конфигурацию для выполнения повторяющихся действий, таких как генерация HTML из Markdown или определение города по IP. Конфигурация осуществляется через передачу опций в конструктор объекта, а сами опции хранятся внутри и используются для всех последующих вызовов.
# Инициализируем объект с заданным таймаутом
request = Request(timeout=1)
response = request.get("<http://example.com>")
Но что, если для конкретного запроса нужно временно установить опции, отличные от тех, что были переданы в конструктор? В этом уроке мы рассмотрим способы временно изменять эту конфигурацию для конкретного вызова.
Создание нового объекта
Мы можем создать новый объект с нужной нам конфигурацией там, где это требуется:
# Инициализируем объект с новым заданным таймаутом
request = Request(timeout=10)
response = request.get("<http://example.com>")
Здесь мы создаем новый объект Request
с новым параметром таймаута. Затем выполняется HTTP GET-запрос на http://example.com
с использованием этого объекта.
Это простое решение. Но у него есть ряд недостатков. Главный недостаток — невозможно подменить реализацию, так как объект создается не на этапе конфигурирования системы, а в том месте, где происходит вызов. Из-за этого придется дублировать общие опции, а тестирование станет затруднительным или невозможным.
Здесь речь идет про тот самый полиморфизм, о котором мы будем говорить в будущем.
Теперь рассмотрим следующий способ.
Использование сеттеров
Следующий подход включает использование сеттеров, что позволяет изменять опции уже существующего объекта:
request = Request(timeout=1)
response = request.get("<http://example.com>")
# Изменение параметра таймаута
request.set_timeout(10)
response = request.get("<http://example.com>")
В данном случае мы начинаем с того же объекта Request
, что и в предыдущем примере. Но теперь мы используем сеттер set_timeout()
, чтобы изменить таймаут с одной до десяти секунд для этого объекта. Затем мы снова выполняем GET-запрос, и этот запрос теперь будет использовать новое значение таймаута.
Изменяемое состояние — это одна из самых сложных концепций в программировании. Именно оно может привести к большинству проблем, с которыми сталкиваются программисты. Оно может создать трудноуловимые и опасные баги.
Объект request
используется совместно всеми частями системы. Это означает, что его изменение в одном месте повлияет на все последующие вызовы.
В реальной жизни такие вещи могут приводить к еще более серьезным последствиям. Допустим, мы работаем с классом MarkdownRenderer
, который отвечает за рендеринг markdown в HTML. Этот класс имеет опцию sanitize
, которая отвечает за включение или отключение безопасного рендеринга.
Если мы случайно отключим эту опцию, то теги <script>
, вставленные в Markdown, отобразятся как есть. Это может быть допустимо для текста, который мы контролируем, например, для уроков на нашем сайте. Но это недопустимо для текста, введенного пользователями. Изменение опции sanitize
в объекте MarkdownRenderer
может создать уязвимость для межсайтового скриптинга (XSS).
Чтобы избежать этого, мы можем попытаться вернуть опцию обратно после того, как мы ее изменили:
md = MarkdownRenderer(sanitize=True)
# В одной части программы
html = md.render(markdown)
# В другой части программы отключаем санитайз
md.set_sanitize(False)
html = md.render(user_input)
md.set_sanitize(True)
При этом программист может забыть это сделать. Код, в котором сначала что-то меняется в одну сторону, а затем возвращается обратно, почти всегда указывает на проблемы архитектуры. И существуют способы переписать его более безопасным образом.
Передача опций при каждом вызове
Лучшим решением данной проблемы может быть передача дополнительных параметров при каждом вызове метода:
request = Request(timeout=1)
response = request.get("<http://example.com>")
# Передаем дополнительный параметр при вызове метода
response = request.get("<http://example.com>", timeout=10)
response = request.get("<http://example.com>") # таймаут остается равным 1
В данном случае во втором вызове метода get
мы передаем дополнительный параметр timeout
со значением 10
. Это изменение затрагивает только данный вызов, а не весь объект request
.
Таким образом, когда мы возвращаемся к третьему вызову метода get
, первоначальное значение timeout
(1
) остается неизменным. Это предотвращает возможные проблемы, связанные с изменением состояния объекта. Еще это позволяет гибко управлять настройками для каждого вызова.
Выводы
В этом уроке мы обсудили различные подходы к управлению настройками объекта: от создания нового объекта до использования сеттеров и передачи дополнительных параметров при каждом вызове метода. Передача опций при каждом вызове — наиболее предпочтительный подход, так как он избегает проблем, связанных с изменяемым состоянием, и позволяет сохранять гибкость кода.
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.