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

Об'єктний пул (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-з'єднання, авторизація, рукостискання (handshake). Це займає мілісекунди. Якщо відкривати з'єднання на кожен запит, сайт буде гальмувати. Тому SQLAlchemy використовує Connection Pool.
  • Потоки та процеси: Створення нового процесу операційної системи — важка операція. Тому ми використовуємо ThreadPoolExecutor або воркери Celery/Gunicorn. Вони створюються один раз під час старту і потім просто обробляють завдання з черги.

Підсумок: використовуйте пул, коли вартість створення об'єкта («ціна квитка») занадто висока, щоб платити її щоразу.

Пам'ятайте про GIL: пули потоків гарні для I/O операцій, а для обчислень краще використовувати пули процесів. Це продемонструє вашу технічну грамотність.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ