Python: Объектно-ориентированный дизайн
2026-02-26 17:31 Diff

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

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

Создание нового объекта

Мы можем создать новый объект с нужной нам конфигурацией там, где это требуется:

Здесь мы создаем новый объект Request с новым параметром таймаута. Затем выполняется HTTP GET-запрос на http://example.com с использованием этого объекта.

Это простое решение. Но у него есть ряд недостатков. Главный недостаток — невозможно подменить реализацию, так как объект создается не на этапе конфигурирования системы, а в том месте, где происходит вызов. Из-за этого придется дублировать общие опции, а тестирование станет затруднительным или невозможным.

Здесь речь идет про тот самый полиморфизм, о котором мы будем говорить в будущем.

Теперь рассмотрим следующий способ.

Использование сеттеров

Следующий подход включает использование сеттеров, что позволяет изменять опции уже существующего объекта:

В данном случае мы начинаем с того же объекта Request, что и в предыдущем примере. Но теперь мы используем сеттер set_timeout(), чтобы изменить таймаут с одной до десяти секунд для этого объекта. Затем мы снова выполняем GET-запрос, и этот запрос теперь будет использовать новое значение таймаута.

Изменяемое состояние — это одна из самых сложных концепций в программировании. Именно оно может привести к большинству проблем, с которыми сталкиваются программисты. Оно может создать трудноуловимые и опасные баги.

Объект request используется совместно всеми частями системы. Это означает, что его изменение в одном месте повлияет на все последующие вызовы.

В реальной жизни такие вещи могут приводить к еще более серьезным последствиям. Допустим, мы работаем с классом MarkdownRenderer, который отвечает за рендеринг markdown в HTML. Этот класс имеет опцию sanitize, которая отвечает за включение или отключение безопасного рендеринга.

Если мы случайно отключим эту опцию, то теги <script>, вставленные в Markdown, отобразятся как есть. Это может быть допустимо для текста, который мы контролируем, например, для уроков на нашем сайте. Но это недопустимо для текста, введенного пользователями. Изменение опции sanitize в объекте MarkdownRenderer может создать уязвимость для межсайтового скриптинга (XSS).

Чтобы избежать этого, мы можем попытаться вернуть опцию обратно после того, как мы ее изменили:

При этом программист может забыть это сделать. Код, в котором сначала что-то меняется в одну сторону, а затем возвращается обратно, почти всегда указывает на проблемы архитектуры. И существуют способы переписать его более безопасным образом.

Передача опций при каждом вызове

Лучшим решением данной проблемы может быть передача дополнительных параметров при каждом вызове метода:

В данном случае во втором вызове метода get мы передаем дополнительный параметр timeout со значением 10. Это изменение затрагивает только данный вызов, а не весь объект request.

Таким образом, когда мы возвращаемся к третьему вызову метода get, первоначальное значение timeout (1) остается неизменным. Это предотвращает возможные проблемы, связанные с изменением состояния объекта. Еще это позволяет гибко управлять настройками для каждого вызова.

Выводы

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