Паттерны проектирования в 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]
Какой паттерн выбрать? 🧐
- Singleton — когда нужен строго один экземпляр класса.
- Factory — если создание объектов сложное или требует гибкости.
- 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()
Этот пример показывает, как паттерны могут работать вместе, создавая гибкую архитектуру!
Вывод: Паттерны — это суперсила 🦸♂️
Освоив эти три паттерна, вы уже сможете: - Контролировать создание объектов - Управлять алгоритмами - Гарантировать единственность экземпляров
Главное — практиковаться! Попробуйте реализовать каждый паттерн в своём проекте и почувствуйте разницу в архитектуре кода.