JavaRush /Курсы /Architecture & Logic /Порождающие паттерны, часть 2

Порождающие паттерны, часть 2

Architecture & Logic
1 уровень , 3 лекция
Открыта

Builder

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

    classDiagram
        direction LR
        class Director {
            -builder
            +construct_sports_car()
            +construct_suv()
        }
        class Builder {
            <<Interface>>
            +reset()
            +set_engine()
            +set_wheels()
            +get_result()
        }
        class CarBuilder {
            +set_engine()
            +set_wheels()
            +get_result()
        }
        class CarManualBuilder {
            +set_engine()
            +set_wheels()
            +get_result()
        }

        Director --> Builder
        CarBuilder ..|> Builder
        CarManualBuilder ..|> Builder
    

Named Arguments vs Real Builder

В Java паттерн Builder часто используют, просто чтобы избавиться от конструкторов с 10 аргументами. В Python эта проблема решена на уровне языка с помощью именованных аргументов:

Car(color="red", engine="V8", gps=True) — это удобно и читаемо.

Но настоящий паттерн Builder нужен, когда процесс создания сложен и состоит из этапов. Лучший пример, который вы знаете — Django ORM:

User.objects.filter(is_active=True).exclude(role='guest').order_by('-date_joined')

Здесь QuerySet выступает в роли Строителя. Вы пошагово «настраиваете» будущий SQL-запрос. И только в самом конце, когда данные действительно нужны (метод get_result в паттерне или итерация в Django), объект «строится» и выполняется запрос к БД.

Lazy Initialization

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

Логика Lazy Initialization

    flowchart LR
        Start([Запрос атрибута]) --> Check{Уже создан?}
        Check -- Да --> Return[Вернуть сохраненное значение]
        Check -- Нет --> Calc[Выполнить тяжелую операцию]
        Calc --> Save[Сохранить результат]
        Save --> Return
    

В веб-разработке это критически важно. Представьте, что при запуске Django-проекта мы бы сразу загружали все данные из всех таблиц БД в память. Сервер бы стартовал час. Вместо этого мы используем ленивую загрузку.

Python-way: @cached_property

В Python этот паттерн реализуется невероятно элегантно через декоратор из стандартной библиотеки functools:

from functools import cached_property

class UserProfile:
    @cached_property
    def heavy_statistics(self):
        print("Считаю сложную статистику...")
        return 42

p = UserProfile()
print(p.heavy_statistics) # Выполнит код и сохранит 42
print(p.heavy_statistics) # Вернет сразу 42, не выполняя код

Сильные стороны:

  • Ускоряет старт приложения (не делаем лишнюю работу сразу).
  • Экономит ресурсы, если тяжелое поле так и не понадобилось пользователю.

Object Pool

Объектный пул — паттерн, который хранит набор уже инициализированных объектов, готовых к работе. Вместо того чтобы создавать объект с нуля (что долго), мы берем его из пула, используем и возвращаем обратно.

    sequenceDiagram
        participant Client
        participant Pool
        participant Database

        Client->>Pool: Дай соединение!
        alt В пуле есть свободное
            Pool-->>Client: Вот, держи (Connection #1)
        else Пул пуст
            Pool->>Database: Создать новое соединение
            Database-->>Pool: Connection #2
            Pool-->>Client: Вот, держи (Connection #2)
        end
        Client->>Database: SQL Query...
        Client->>Pool: Я всё, забирай (Connection #1)
    

В Python создание обычных объектов (строк, списков, классов) работает очень быстро, и пул для них не нужен (об этом заботится сборщик мусора). Но есть ресурсы, создание которых стоит очень дорого:

  • Соединения с базой данных: Открытие TCP-соединения, авторизация, рукопожатие. Это занимает миллисекунды. Если открывать соединение на каждый запрос, сайт будет тормозить. Поэтому SQLAlchemy использует Connection Pool.
  • Потоки и процессы: Создание нового процесса операционной системы — тяжелая операция. Поэтому мы используем ThreadPoolExecutor или воркеры Celery/Gunicorn. Они создаются один раз при старте и потом просто обрабатывают задачи из очереди.

Итог: используйте пул, когда стоимость создания объекта («цена билета») слишком высока, чтобы платить её каждый раз.

Помните про GIL: пулы потоков хороши для I/O операций, а для вычислений лучше пулы процессов". Это покажет вашу техническую грамотность

Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ