Перейти к содержанию

Шаблоны проектирования

Справочник вопросов по классическим шаблонам проектирования (GoF) для подготовки к экзамену.

1. Шаблоны проектирования: что это и типы шаблонов

Шаблон проектирования — типовое, проверенное практикой решение часто встречающейся задачи проектирования в заданном контексте; это не готовый код, а описание подхода. Шаблоны GoF делятся на три типа: порождающие (creational) — отвечают за гибкое создание объектов; структурные (structural) — описывают сборку объектов и классов в более крупные структуры; поведенческие (behavioral) — распределяют обязанности и организуют взаимодействие между объектами.

Порождающие

2. Простая фабрика (Simple Factory)

Назначение: инкапсулировать выбор и создание конкретного объекта в одной функции/методе, чтобы клиент не зависел от конкретных классов. Когда применять: когда логика выбора реализации по параметру (например, создание нужного клиента хранилища make_storage("s3")) повторяется и её хочется собрать в одном месте. Формально не входит в каталог GoF, но служит базой для фабричного метода.

3. Фабричный метод (Factory Method)

Назначение: определить интерфейс для создания объекта, но делегировать выбор конкретного класса подклассам, переопределяющим метод-фабрику. Когда применять: когда базовый алгоритм фиксирован, но тип создаваемого продукта должен задаваться наследником (например, разные Logger-ы создают разные форматтеры записей).

4. Абстрактная фабрика (Abstract Factory)

Назначение: предоставить интерфейс для создания целого семейства связанных объектов без привязки к их конкретным классам. Когда применять: когда нужно гарантировать совместимость группы продуктов и переключать всё семейство разом (например, фабрика «клиентов облака» сразу даёт согласованные Compute, Storage, Network для AWS или GCP).

5. Строитель (Builder)

Назначение: пошагово конструировать сложный объект, отделяя процесс сборки от итогового представления. Когда применять: когда у объекта много опциональных параметров и комбинаций (например, конфиг HTTP-клиента или запрос с таймаутами, ретраями, заголовками), а телескопические конструкторы становятся нечитаемыми — отсюда популярный fluent-стиль b.WithTimeout(...).WithRetries(...).Build().

6. Прототип (Prototype)

Назначение: создавать новые объекты копированием (клонированием) существующего экземпляра-прототипа вместо вызова конструктора. Когда применять: когда инициализация объекта дорогая или конфигурация сложна, и проще склонировать уже настроенный образец, слегка изменив его (например, копия преднастроенного шаблона ресурса/манифеста).

7. Одиночка (Singleton)

Назначение: гарантировать, что у класса есть ровно один экземпляр, и предоставить к нему глобальную точку доступа. Когда применять: для разделяемых ресурсов в едином состоянии — пул соединений, конфигурация, логгер; в многопоточной среде требует потокобезопасной инициализации (в Go — sync.Once). Часто критикуется как скрытая глобальная зависимость, усложняющая тестирование.

Структурные

8. Адаптер (Adapter)

Назначение: преобразовать интерфейс существующего класса в интерфейс, ожидаемый клиентом, «обёрткой» поверх несовместимого API. Когда применять: при интеграции стороннего или legacy-кода, когда менять его нельзя, а вызывать нужно через общий интерфейс (например, обернуть SDK провайдера под свой интерфейс MetricsSink).

9. Мост (Bridge)

Назначение: разделить абстракцию и её реализацию на две независимые иерархии, чтобы они менялись отдельно друг от друга. Когда применять: когда есть два измерения вариативности, и комбинировать их наследованием — комбинаторный взрыв классов (например, «тип уведомления» × «канал доставки»: абстракция Notification держит ссылку на реализацию Sender).

10. Компоновщик (Composite)

Назначение: объединить объекты в древовидную структуру и работать с отдельным элементом и группой единообразно, через общий интерфейс. Когда применять: для иерархий «часть–целое», где клиент не должен различать лист и контейнер (например, файловая система, дерево ресурсов или вложенные группы правил).

11. Фасад (Facade)

Назначение: предоставить единый упрощённый интерфейс к сложной подсистеме, скрыв её внутреннее устройство. Когда применять: когда набор низкоуровневых вызовов нужно свести к понятной операции верхнего уровня (например, метод Deploy() поверх десятка шагов сборки, выкладки и проверки здоровья).

12. Приспособленец (Flyweight)

Назначение: экономить память, разделяя общее (внутреннее) состояние между множеством мелких объектов и вынося различающееся (внешнее) состояние наружу. Когда применять: когда объектов очень много и они дублируют одинаковые данные (например, переиспользуемые описания типов метрик/лейблов для миллионов точек данных).

13. Заместитель (Proxy)

Назначение: подставить вместо реального объекта объект-заместитель с тем же интерфейсом, чтобы контролировать доступ к нему. Когда применять: для отложенной (ленивой) инициализации, кэширования, контроля доступа или логирования вокруг дорогого/удалённого ресурса (например, кэширующий или защищающий прокси перед удалённым API).

Поведенческие

14. Состояние (State)

Назначение: позволить объекту менять поведение при изменении внутреннего состояния так, будто меняется его класс — каждое состояние выносится в отдельный объект. Когда применять: когда поведение объекта сильно зависит от состояния и иначе разрастаются ветвления if/switch (например, жизненный цикл задачи: Pending → Running → Failed/Done).

15. Шаблонный метод (Template Method)

Назначение: задать в базовом классе скелет алгоритма, оставив переопределение отдельных шагов подклассам. Когда применять: когда несколько вариантов отличаются лишь частью шагов общего процесса (например, базовый пайплайн extract → transform → load, где наследники переопределяют только transform).

16. Стратегия (Strategy)

Назначение: определить семейство взаимозаменяемых алгоритмов, инкапсулировать каждый и сделать их подменяемыми во время выполнения. Когда применять: когда нужно выбирать способ решения задачи на лету, не плодя условия (например, стратегия ретраев или балансировки, передаваемая в клиент как параметр/функция).

17. Посетитель (Visitor)

Назначение: вынести операцию из иерархии объектов в отдельный класс-посетитель, чтобы добавлять новые операции без изменения самих объектов. Когда применять: когда структура классов стабильна, а набор операций над ней часто расширяется (например, обход AST/дерева конфигурации разными анализаторами и валидаторами).

18. Наблюдатель (Observer)

Назначение: установить отношение «один-ко-многим», при котором изменение состояния субъекта автоматически уведомляет всех подписчиков. Когда применять: для реактивных уведомлений об изменениях без жёсткой связности (например, рассылка событий о смене статуса сервиса подписчикам — алертам, дашбордам, логам).

19. Хранитель (Memento)

Назначение: сохранять и восстанавливать внутреннее состояние объекта, не нарушая его инкапсуляции. Когда применять: когда нужны откат/undo или снимки состояния (например, сохранение состояния конфигурации перед изменением для отката при сбое).

20. Цепочка обязанностей (Chain of Responsibility)

Назначение: передавать запрос по цепочке обработчиков, пока один из них его не обработает, ослабляя связь отправителя и получателя. Когда применять: когда запрос может обрабатываться разными способами в зависимости от условий (например, конвейер middleware HTTP-запроса: аутентификация → лимиты → логирование → обработчик).

21. Команда (Command)

Назначение: инкапсулировать запрос в виде объекта, что позволяет параметризовать, ставить в очередь, логировать и отменять операции. Когда применять: когда действия нужно откладывать, ставить в очередь, повторять или отменять (например, очередь задач/джобов, где каждая команда — самодостаточный объект с методом Execute()).

22. Посредник (Mediator)

Назначение: ввести объект-посредник, через который общаются компоненты, чтобы они не ссылались друг на друга напрямую и снизилась связность. Когда применять: когда множество объектов взаимодействуют по принципу «все со всеми» и связи становятся неуправляемыми (например, координатор, через который компоненты системы обмениваются командами вместо прямых вызовов).

См. также