Инженерия ПО
Design Patterns: GoF
1994 год. Гамма, Хельм, Джонсон, Влиссидес - 'банда четырёх' (Gang of Four). Книга Design Patterns. 23 паттерна. Стала одной из самых продаваемых книг по программированию. Через 30 лет половина из них вошла в стандартные библиотеки языков. PyTorch nn.Module - это Composite паттерн. Каждый слой нейросети - leaf node. Sequential - composite node. model.parameters() обходит всё дерево через единый интерфейс. Паттерн из 1994 года в коде 2024 года.
- **PyTorch:** nn.Module (Composite) + autograd hooks (Observer) + optim (Strategy) - три GoF паттерна в одном фреймворке
- **Hugging Face transformers:** AutoModel (Abstract Factory) + Pipeline (Facade) + Trainer callbacks (Observer)
- **FastAPI:** Middleware (Decorator chain) + Dependency Injection (Strategy/DIP variant) + Router (Composite)
- **LangChain:** Chain (Composite) + Tool (Command) + Memory (Strategy) + BaseLLM (Adapter)
Creational Patterns
1994 год. Гамма, Хельм, Джонсон, Влиссидес - 'банда четырёх' (Gang of Four). Книга Design Patterns. 23 паттерна. Стала одной из самых продаваемых книг по программированию. Через 30 лет половина из них вошла в стандартные библиотеки языков. Creational паттерны решают одну проблему: как создавать объекты, не привязываясь к конкретным классам. **Factory Method** - метод создаёт объект, но дочерний класс решает какой именно. **Abstract Factory** - семейство связанных объектов без указания конкретных классов. **Builder** - пошаговое создание сложного объекта, отделяя конструкцию от представления. **Singleton** - единственный экземпляр класса, глобальная точка доступа - и самый критикуемый паттерн GoF.
**Singleton - самый опасный GoF паттерн.** Это глобальное состояние с ООП-маской. Unit-тесты сломаны: нельзя изолировать компонент от глобального синглтона. Параллельность сломана: race conditions без явного mutexа. GoF книга сама называет Singleton 'не лучшим дизайном'. Современная альтернатива: Dependency Injection - передавать зависимость явно через конструктор вместо `getInstance()`.
Hugging Face `AutoModel.from_pretrained('bert-base-uncased')` возвращает `BertModel`, а `AutoModel.from_pretrained('gpt2')` возвращает `GPT2Model`. Какой GoF паттерн это реализует?
Structural Patterns
Structural паттерны решают задачу компоновки классов и объектов в более крупные структуры. **Adapter** - несовместимые интерфейсы начинают работать вместе через класс-переходник. **Decorator** - динамическое добавление поведения без изменения исходного класса. **Composite** - дерево объектов с единым интерфейсом для листьев и узлов. **Facade** - упрощённый интерфейс к сложной подсистеме. PyTorch `nn.Module` - это Composite: каждый слой сети - leaf node, `nn.Sequential` - composite node, `model.parameters()` обходит всё дерево через единый интерфейс. Паттерн из 1994 года в коде 2024 года.
**Composite в нейросетях:** идея Composite - единый интерфейс для атомарных и составных элементов - идеально ложится на иерархию слоёв. Один слой `Linear` и модель из 1000 слоёв имеют одинаковый интерфейс `forward()` и `parameters()`. Это позволяет строить произвольно глубокие сети через композицию без специальных случаев в коде обучения.
В PyTorch `nn.Sequential(nn.Linear(128, 64), nn.ReLU(), nn.Dropout(0.3))` можно вложить внутрь другого `nn.Sequential`. `model.parameters()` рекурсивно вернёт параметры всех уровней. Какой GoF паттерн это?
Behavioral Patterns
Behavioral паттерны описывают взаимодействие объектов и распределение ответственностей. **Observer** - объекты подписываются на события и получают уведомления при изменении состояния. **Strategy** - семейство взаимозаменяемых алгоритмов за общим интерфейсом. **Command** - инкапсуляция запроса как объекта, позволяющая откладывать и отменять операции. **Iterator** - последовательный доступ к элементам без раскрытия внутренней структуры. Парадокс: Strategy pattern в Python - это просто передача функции как аргумент. В Java 8 появились лямбды. Половина GoF паттернов стала ненужной потому что Python/JS/Kotlin поддержали first-class functions с самого начала.
**Python генераторы делают Iterator паттерн тривиальным.** В Java Iterator - это отдельный класс с `hasNext()` и `next()`. В Python - `yield` в функции, встроенная поддержка `for x in obj:`. Это не означает что паттерн исчез - он встроен в язык. DataLoader, файловые итераторы, pandas iterrows - всё это Iterator под другим именем.
Hugging Face Trainer принимает список `callbacks`: EarlyStoppingCallback, WandBCallback, LRSchedulerCallback. Каждый получает уведомления о событиях обучения. Какой GoF паттерн?
Когда применять и когда нет
Кристофер Александр, автор оригинальной идеи pattern language: 'Паттерн - это решение проблемы в контексте.' Не универсальное правило - решение конкретной проблемы в конкретном контексте. Главный антипаттерн - применение GoF паттернов потому что 'так правильно', не потому что есть реальная проблема. Результат: FactoryManagerFactoryBuilder для создания одного объекта. YAGNI (You Aren't Gonna Need It): не добавлять абстракцию пока нет второй реализации. Первый раз - пишем прямо. Второй раз - рефакторинг. В ML-системах паттерны появляются естественно: Strategy для выбора модели, Observer для training callbacks, Command для job queue. Не нужно их форсировать.
**Паттерны как словарь, не как цель.** GoF паттерны - это общий язык команды: сказать 'используем Observer для событий обучения' быстрее чем объяснять архитектуру на словах. Цель не 'добавить больше паттернов' - цель решить конкретную задачу с наименьшей сложностью. Если задача решается прямым кодом - прямой код лучше.
Чем больше GoF паттернов в коде - тем лучше архитектура
Паттерны - это словарь для описания решений, не цель сама по себе. Хорошая архитектура решает задачу с минимальной необходимой сложностью.
Over-engineering с паттернами - реальная проблема. FactoryManagerFactoryBuilder встречается в production коде. Паттерн решает конкретную проблему в конкретном контексте. Нет проблемы - нет паттерна. Functional style в Python/JS/Kotlin часто заменяет целые классы GoF паттернов одной функцией первого класса.
ML-инженер добавляет второй LLM-провайдер (Anthropic) к существующему коду, который работал только с OpenAI. Когда правильный момент ввести Strategy паттерн?
Ключевые идеи
- **Creational** (Factory, Builder, Singleton): управление созданием объектов. Singleton - самый известный и самый опасный: глобальное состояние с ООП-маской
- **Structural** (Adapter, Decorator, Composite, Facade): компоновка объектов. nn.Module в PyTorch - Composite паттерн в действии
- **Behavioral** (Observer, Strategy, Command, Iterator): распределение ответственностей. В Python многие из них - просто callable или generator
- **Когда применять:** при появлении второй реализации (Strategy), при необходимости уведомлений (Observer), при дереве объектов (Composite). Не раньше - YAGNI
Связанные темы
GoF паттерны проходят через весь стек разработки:
- SOLID принципы — GoF паттерны - конкретные реализации SOLID. Strategy реализует OCP, Dependency Injection реализует DIP.
- Clean Architecture — GoF паттерны как строительные блоки чистой архитектуры: Repository (Adapter + Strategy), Use Cases (Command)
- AI Engineering: API Integration — LLM провайдеры через Adapter, параметры запросов через Builder, выбор модели через Strategy
- Теория языков программирования — First-class functions и lambda в современных языках заменяют половину GoF паттернов
- Строки и коллекции — Python генераторы - встроенный Iterator паттерн. for x in obj: это GoF Iterator под синтаксическим сахаром
Вопросы для размышления
- PyTorch Trainer принимает список callbacks (EarlyStopping, ModelCheckpoint, LRScheduler). Какой GoF паттерн это реализует? Чем он лучше чем if/else внутри training loop?
- В Python Strategy паттерн часто заменяется передачей функции как аргумента. Когда оправдано создавать полноценный класс-стратегию вместо callable?
- Singleton считается антипаттерном в тестируемом коде. Какие реальные use cases оправдывают его применение и как минимизировать риски?