Паттерны проектирования в Python: Singleton, Factory, Strategy

Почему паттерны проектирования — это мощный инструмент? 🤯

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

Python с его простым синтаксисом идеально подходит для изучения паттернов. Сегодня разберём три ключевых:


🏰 Singleton (Одиночка): Один на весь мир

Суть: Гарантирует, что у класса есть только один экземпляр, и предоставляет к нему глобальную точку доступа.

Где применяется?

  • Логгеры (один логгер на всё приложение)
  • Подключения к базе данных
  • Конфигурационные файлы

Реализация в Python

class Singleton:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance.init_singleton()
        return cls._instance

    def init_singleton(self):
        self.data = "Я единственный!"

# Использование
s1 = Singleton()
s2 = Singleton()
print(s1 is s2)  # True — это один и тот же объект!

🔎 Важно: В Python есть более элегантные способы через декораторы или метаклассы (Данила Бежин рассказывает об этом в своём видео).


🏭 Factory (Фабрика): Производство объектов под ключ

Суть: Делегирует создание объектов отдельному методу или классу (фабрике), а не создаёт их напрямую через конструктор.

Зачем?

  • Когда создание объекта сложное (много параметров)
  • Нужна гибкость в выборе класса (например, разные ОС)
  • Хотим отделить логику создания от основной бизнес-логики

Пример: Фабрика транспорта

class Transport:
    def deliver(self):
        pass

class Truck(Transport):
    def deliver(self):
        return "Грузовик везёт по дороге"

class Ship(Transport):
    def deliver(self):
        return "Корабль плывёт по морю"

class TransportFactory:
    @staticmethod
    def create_transport(type_):
        if type_ == "truck":
            return Truck()
        elif type_ == "ship":
            return Ship()
        raise ValueError("Неизвестный тип транспорта")

# Клиентский код
transport = TransportFactory.create_transport("ship")
print(transport.deliver())  # Корабль плывёт по морю

🎮 Strategy (Стратегия): Меняем алгоритмы на лету

Суть: Определяет семейство алгоритмов, инкапсулирует каждый из них и делает их взаимозаменяемыми.

Преимущества:

  • Лёгкая замена алгоритмов без изменения клиентского кода
  • Избавляет от множества условных операторов (if-else)

Пример: Сортировка данных

from typing import List

class SortStrategy:
    def sort(self, data: List) -> List:
        pass

class QuickSort(SortStrategy):
    def sort(self, data: List) -> List:
        return sorted(data)  # Быстрая сортировка (упрощённо)

class ReverseSort(SortStrategy):
    def sort(self, data: List) -> List:
        return sorted(data, reverse=True)

class Sorter:
    def __init__(self, strategy: SortStrategy):
        self._strategy = strategy

    def set_strategy(self, strategy: SortStrategy):
        self._strategy = strategy

    def execute_sort(self, data: List) -> List:
        return self._strategy.sort(data)

# Использование
data = [5, 2, 8, 1]
sorter = Sorter(QuickSort())
print(sorter.execute_sort(data))  # [1, 2, 5, 8]

sorter.set_strategy(ReverseSort())
print(sorter.execute_sort(data))  # [8, 5, 2, 1]

Какой паттерн выбрать? 🧐

  1. Singleton — когда нужен строго один экземпляр класса.
  2. Factory — если создание объектов сложное или требует гибкости.
  3. Strategy — когда есть несколько вариантов алгоритма и нужно переключаться между ними.

💡 Профессиональный совет: Не используйте паттерны везде «просто потому что». Применяйте их только тогда, когда проблема действительно требует такого решения!


Практика: Комбинируем паттерны 💥

Давайте создадим кэш-систему, которая: - Использует Singleton для доступа к кэшу - Применяет Strategy для разных алгоритмов очистки (например, LRU, FIFO) - Использует Factory для создания кэш-записей разного типа

class CacheCleanStrategy:
    def clean(self, cache):
        pass

class LRUStrategy(CacheCleanStrategy):
    def clean(self, cache):
        print("Очистка по LRU")

class Cache:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance._strategy = LRUStrategy()  # По умолчанию
        return cls._instance

    def set_clean_strategy(self, strategy):
        self._strategy = strategy

    def clean(self):
        self._strategy.clean(self)

# Пример использования
cache = Cache()
cache.set_clean_strategy(LRUStrategy())
cache.clean()

Этот пример показывает, как паттерны могут работать вместе, создавая гибкую архитектуру!


Вывод: Паттерны — это суперсила 🦸‍♂️

Освоив эти три паттерна, вы уже сможете: - Контролировать создание объектов - Управлять алгоритмами - Гарантировать единственность экземпляров

Главное — практиковаться! Попробуйте реализовать каждый паттерн в своём проекте и почувствуйте разницу в архитектуре кода.

Скрыть рекламу навсегда

🧠 Учёба без воды и зубрёжки

Закрытый Boosty с наработками опытного преподавателя.

Объясняю сложное так, чтобы щелкнуло.

🚀 Забрать доступ к Boosty