Какие паттерны проектирования из GoF наиболее часто применяются в Python

Ответ

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

  1. Декоратор (Decorator) В Python этот паттерн реализован на уровне синтаксиса (@decorator), что делает его чрезвычайно популярным для добавления функциональности (логирование, кэширование, проверка прав) к функциям и методам, не изменяя их код.

    import time
    
    def timing_decorator(func):
        def wrapper(*args, **kwargs):
            start_time = time.time()
            result = func(*args, **kwargs)
            end_time = time.time()
            print(f"'{func.__name__}' выполнилась за {end_time - start_time:.4f} секунд")
            return result
        return wrapper
    
    @timing_decorator
    def get_data():
        time.sleep(1)
        return "Some data"
    
    get_data()
    # Вывод: 'get_data' выполнилась за 1.0012 секунд
  2. Фабричный метод (Factory Method) Позволяет создавать объекты, делегируя логику инстанцирования подклассам. Это полезно, когда система должна работать с объектами разных типов, но их точный тип определяется во время выполнения.

    class Document:
        # ... базовый класс
    
    class PdfDocument(Document):
        # ... реализация для PDF
    
    class WordDocument(Document):
        # ... реализация для Word
    
    def get_document_creator(file_extension):
        if file_extension == 'pdf':
            return PdfDocument
        elif file_extension == 'docx':
            return WordDocument
        raise ValueError("Unknown format")
    
    # Использование
    DocCreator = get_document_creator('pdf')
    my_doc = DocCreator()
  3. Стратегия (Strategy) Определяет семейство алгоритмов, инкапсулирует каждый из них и делает их взаимозаменяемыми. Часто используется для реализации различных способов обработки данных (сортировка, экспорт).

    from abc import ABC, abstractmethod
    
    class ExportStrategy(ABC):
        @abstractmethod
        def export(self, data):
            pass
    
    class JsonExport(ExportStrategy):
        def export(self, data):
            print(f"Экспорт в JSON: {data}")
    
    class XMLExport(ExportStrategy):
        def export(self, data):
            print(f"Экспорт в XML: {data}")
    
    # Контекст, использующий стратегию
    class Exporter:
        def __init__(self, strategy: ExportStrategy):
            self._strategy = strategy
    
        def run(self, data):
            self._strategy.export(data)
    
    data_to_export = {"id": 1, "name": "item"}
    exporter = Exporter(JsonExport())
    exporter.run(data_to_export)
  4. Наблюдатель (Observer) Создает механизм подписки, позволяющий одним объектам следить и реагировать на события, происходящие в других объектах. Основа для многих event-driven систем.

  5. Одиночка (Singleton) Гарантирует, что у класса есть только один экземпляр. В Python часто считается антипаттерном, так как его функциональность можно реализовать более идиоматично через модули, которые по своей природе являются синглтонами.

Ответ 18+ 🔞

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

Вот, например, Декоратор. Это вообще, блядь, визитная карточка языка. Синтаксис такой милый, собачка @, а под капотом — мощнейший инструмент, чтобы обернуть функцию, не трогая её саму. Логирование, замер времени, кэширование — всё туда.

import time

def timing_decorator(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"'{func.__name__}' выполнилась за {end_time - start_time:.4f} секунд")
        return result
    return wrapper

@timing_decorator
def get_data():
    time.sleep(1)
    return "Some data"

get_data()
# Вывод: 'get_data' выполнилась за 1.0012 секунд

Красота же, блядь! Навесил собачку и функция уже не просто работает, а ещё и отчитывается, сколько она там прохлаждалась.

Дальше, Фабричный метод. Ситуация классическая: «Ой, а что создавать-то будем? А хрен его знает, узнаем в рантайме». Вот этот паттерн как раз про то, чтобы не городить if-ов на каждом углу, а вынести эту логику в отдельное место.

class Document:
    # ... базовый класс

class PdfDocument(Document):
    # ... реализация для PDF

class WordDocument(Document):
    # ... реализация для Word

def get_document_creator(file_extension):
    if file_extension == 'pdf':
        return PdfDocument
    elif file_extension == 'docx':
        return WordDocument
    raise ValueError("Unknown format")

# Использование
DocCreator = get_document_creator('pdf')
my_doc = DocCreator()

Типа, «дай-ка мне фабрику для пдфшек» — и вот тебе, сука, готовый конструктор. Удобно, не приходится мозг ломать.

А вот Стратегия — это вообще песня. Когда у тебя есть, например, пять разных способов отсортировать данные или экспортировать их в файл. И вместо того, чтобы делать монструозную функцию с кучей switch-ей, ты каждый алгоритм в отдельный класс засовываешь.

from abc import ABC, abstractmethod

class ExportStrategy(ABC):
    @abstractmethod
    def export(self, data):
        pass

class JsonExport(ExportStrategy):
    def export(self, data):
        print(f"Экспорт в JSON: {data}")

class XMLExport(ExportStrategy):
    def export(self, data):
        print(f"Экспорт в XML: {data}")

# Контекст, использующий стратегию
class Exporter:
    def __init__(self, strategy: ExportStrategy):
        self._strategy = strategy

    def run(self, data):
        self._strategy.export(data)

data_to_export = {"id": 1, "name": "item"}
exporter = Exporter(JsonExport())
exporter.run(data_to_export)

Меняешь стратегию на лету — и весь остальной код даже не подозревает, что что-то поменялось. Хитро, блядь!

Наблюдатель — это про то, чтобы все узнали, что Васёк, наконец, закоммитил код без багов. Создаёшь механизм подписки: «кто хочет следить за этим идиотом?». И когда событие случается — всех оповещаешь. Основа для всего event-driven, без этого никуда.

Ну и куда же без Одиночки. Гарантирует, что класс будет в единственном экземпляре. Хотя в Python, если честно, его часто считают антипаттерном, ядрёна вошь! Потому что модули у нас и так по своей природе синглтоны. Зачем городить огород с приватными конструкторами, когда можно просто импортировать модуль и всё? Но некоторые всё равно любят, это ж классика, как «Муму», блядь, все проходили.

Короче, паттерны — это не священная корова, а инструменты. Главное — понимать, зачем они нужны, а не тыкать их куда попало, лишь бы в резюме написать. А то будет как у того Герасима — сила есть, а сказать нечего, только «Му-му» мычать.