← Все статьи

Приёмы ООП (Design Patterns): словарь, а не цель

Выжимка GoF: шаблон как именованное решение, Strategy и Observer сегодня, ловушка «впихнуть паттерн», ИИ и карго-культ — с кейсами и действиями на сегодня.

Приёмы ООП (Design Patterns): словарь, а не цель
Содержание

На ревью два разработчика спорят об одном классе. Один говорит: «здесь нужен Декоратор». Второй: «это просто if». Через месяц в коде — три слоя обёрток, которые никто не может прочитать, и баг в порядке вызовов. Знакомо?

Приёмы объектно-ориентированного проектирования (оригинал Design Patterns, «банда четырёх», GoF, 1994) — не инструкция «вставь шаблон в каждый модуль». Это словарь для повторяющихся проблем проектирования: как назвать решение, обсудить его на ревью и не изобретать велосипед в одиночку. Ниже — разбор своими словами: что из книги живёт в 2026 году, где превращается в карго-культ, и как ИИ усугубляет «pattern soup». Выжимка не заменяет оригинал.

Тезис книги

Шаблон проектирования — это именованное, проверенное решение повторяющейся проблемы в контексте объектно-ориентированных систем. GoF даёт язык: «Strategy», «Observer», «Factory Method» — не как модные слова, а как ссылки на структуру, последствия и компромиссы.

Книга появилась, когда ООП только становился мейнстримом. Многие примеры на C++ и Smalltalk устарели синтаксически, но проблемы те же: как изолировать изменение, как связать объекты без спагетти, как создавать семейства объектов. Главная ошибка читателя — учить 23 шаблона списком и искать место для каждого. Правильнее — узнавать ситуацию и вспоминать имя хода, как шахматист узнаёт дебют.

Ключевые идеи

Шаблон — словарь команды, не KPI

Что говорит автор. Паттерн фиксирует проблему, контекст, решение и последствия. Команда, которая называет «Facade» или «Adapter», экономит десять минут рисования на доске. Цель — общий язык, а не галочка «у нас есть паттерны».

Как это выглядит в промышленном проекте. Интеграция с банком меняет API каждый квартал. Вместо «обёртка над обёрткой» на ревью говорят: «нужен Adapter с узким интерфейсом для нашего домена». Новый разработчик открывает каталог, видит структуру, не переписывает с нуля.

Как это меняется с ИИ. Модель охотно генерирует «AbstractFactory» и «Visitor» там, где хватило бы функции. Дифф выглядит «архитектурно», ревьюер устаёт и пропускает. Полезнее просить: «опиши проблему без паттерна; потом предложи один именованный ход из GoF, если он правда нужен».

Где совет может не работать. В маленькой кодовой базе и простом CRUD общий язык паттернов избыточен — достаточно ясных имён и модулей. Не каждый спор на ревью требует ссылку на GoF.

Что сделать уже сегодня. На следующем ревью спросите: «какую проблему мы решаем?» — до того, как назвали паттерн.

Мой опыт. GoF спасает в разговорах между командами и при онбординге в legacy. Когда паттерн становится целью («нам нужен Observer везде») — книга уже не помогает, мешает.

Если запомнить одну мысль — сначала проблема, потом имя из каталога.

«Программируй на интерфейс, а не на реализацию»

Что говорит автор. Зависимость от абстракции снижает связность: можно подменить реализацию, тестировать с заглушками, развивать подсистемы отдельно. Это сквозная тема creational и structural паттернов.

Как это выглядит в промышленном проекте. Сервис отправки уведомлений зависит от интерфейса Notifier, а не от конкретного SMS-шлюза. Новый канал — новая реализация, домен не перекомпилируется. Без этого каждый if (channel === 'sms') размазывает интegrацию по десяти модулям.

Как это меняется с ИИ. Ассистент генерирует «интерфейс на каждый класс» — зоопарк из одного метода. Абстракция ради абстракции. Критерий остаётся человеческим: есть ли две реализации или реальная перспектива подмены?

Где совет может не работать. YAGNI: интерфейс до второй реализации часто преждевременен. В функциональном стиле и TypeScript union types роль «интерфейса» играет тип — идея та же, форма другая.

Что сделать уже сегодня. Найдите одну зависимость от конкретного класса в зоне текущей задачи — нужна ли подмена в тестах или следующем релизе?

Мой опыт. Это, пожалуй, самая живая мысль GoF в 2026 году. Остальное — частные случаи того, как не прибить код к одной реализации.

Если запомнить одну мысль — интерфейс там, где завтра будет вторая реализация.

Композиция предпочтительнее наследования

Что говорит автор. Наследование жёстко связывает подкласс с базой; композиция (объект содержит другие объекты и делегирует) гибче. Decorator, Strategy, Bridge — про это.

Как это выглядит в промышленном проекте. «Расширить» класс отчёта наследованием для каждого нового формата (PDF, Excel, API) — взрыв иерархии. Strategy для рендерера + композиция данных отчёта — добавили формат без трогания базового класса.

Как это меняется с ИИ. Модель любит глубокие деревья наследования — «выглядит ООП». Человек должен ловить момент, когда пора заменить extends на «поле + делегирование».

Где совет может не работать. Фреймворки с lifecycle (React class components в прошлом, некоторые UI-kit) навязывают наследование. В доменном коде композиция почти всегда выигрывает у глубоких иерархий.

Что сделать уже сегодня. Откройте один extends в текущей задаче — можно ли заменить на композицию без потери ясности?

Мой опыт. GoF здесь стыкуется с рефactoringом Фаулера: «Replace Inheritance with Delegation» — не модная причуда, а следствие той же идеи.

Если запомнить одну мысль — наследование для «is-a» с стабильной семантикой; поведение через композицию.

Creational: гибкость создания, не Singleton ради Singleton

Что говорит автор. Factory Method, Abstract Factory, Builder, Prototype — про то, кто и как создаёт объекты, когда конструктор new недостаточен. Singleton — тоже creational, но с оговорками.

Как это выглядит в промышленном проекте. Builder для сложного документа с десятком опциональных полей читается лучше, чем конструктор на 14 параметров. Factory скрывает выбор реализации PaymentGateway по конфигу. Singleton для «единственного» подключения к БД в монолите — частый источник боли в тестах и при масштабировании.

Как это меняется с ИИ. Агент по умолчанию делает Singleton «для кэша» и глобальное состояние. Ревью должно резать: нужен ли один экземпляр в процессе, или это ленивая привычка.

Где совет может не работать. DI-контейнеры (Spring, Nest, Angular) уже решают создание — паттерны живут внутри фреймворка, не в вашем коде. Builder избыточен для DTO из трёх полей.

Что сделать уже сегодня. Если видите Singleton — спросите: «как мы тестируем это без глобального состояния?»

Мой опыт. Builder и Factory — самые частые живые creational-паттерны у меня. Singleton в новом коде — красный флаг, если только это не OS-level ресурс.

Если запомнить одну мысль — creational-паттерны про варианты создания, не про «красивую фабрику».

Behavioral: Strategy и Observer для изменений

Что говорит автор. Strategy инкапсулирует семейство алгоритмов и делает их взаимозаменяемыми. Observer рассылает изменения подписчикам без жёсткой связи. Command, State, Iterator — другие ответы на «как объекты общаются и меняют поведение».

Как это выглядит в промышленном проекте. Налоговые правила по странам — Strategy, а не switch на 200 строк. UI подписывается на изменение модели — Observer (или его современные формы: события, reactive streams). Без имени паттерна код превращается в «список колбеков, который нельзя отладить».

Как это меняется с ИИ. ИИ генерирует switch/case; рефакторинг в Strategy — хорошая задача для агента после тестов. Observer в микросервисах часто заменён message broker — идея «подписчики не знают друг друга» остаётся.

Где совет может не работать. Observer с сотней подписчиков в одном процессе — проблемы с порядком и утечками памяти. Strategy с одним алгоритмом — лишний слой.

Что сделать уже сегодня. Найдите один большой switch по типу или статусу — кандидат на Strategy?

Мой опыт. Strategy и Observer я узнаю в legacy чаще, чем Abstract Factory. Именно behavioral-паттерны чаще всего остаются в живом коде.

Если запомнить одну мысль — behavioral-паттерны покупают гибкость изменения поведения.

Structural: Adapter и Facade на границах

Что говорит автор. Adapter приводит чужой интерфейс к вашему. Facade даёт простой вход в сложную подсистему. Decorator добавляет обязанности без подклассов. Proxy контролирует доступ.

Как это выглядит в промышленном проекте. Старый SOAP-сервис, новый REST внутри — Adapter для домена. Facade над пятью микросервисами биллинга — один метод «выставить счёт» для фронта. Decorator для логирования и метрик вокруг репозитория — если не превращается в матрёшку.

Как это меняется с ИИ. Модель генерирует Facade, который просто пробрасывает все методы — бесполезная прослойка. Хороший Facade сужает surface area.

Где совет может не работать. Лишний Adapter, когда можно поправить контракт upstream. Decorator-цепочка из пяти слоёв — debugging hell.

Что сделать уже сегодня. На границе с внешним API нарисуйте: «наш интерфейс» vs «их интерфейс» — нужен ли Adapter?

Мой опыт. Facade и Adapter — мои рабочие лошадки в интegraциях. GoF здесь ближе всего к ежедневной работе full-stack команды.

Если запомнить одну мысль — structural-паттерны живут на границах и слоях.

На практике

На ревью

  • Назван ли паттерн после описания проблемы?
  • Есть ли более простое решение без нового слоя?
  • Не смешаны ли «красота ООП» и реальное изменение поведения?
  • Для ИИ-диффа: не раздута ли иерархия классов?

С современным стеком

React hooks, middleware, plugins — часто те же идеи, другой синтаксис. Strategy ≈ prop/callback injection. Observer ≈ pub/sub, RxJS, event emitters. Не ищите класс Observer — ищите структуру зависимостей.

Кому какая идея полезнее

Идея Junior Middle Senior
Паттерн как словарь ★★★★ ★★★★★ ★★★★★
Интерфейс vs реализация ★★★★★ ★★★★★ ★★★★
Композиция vs наследование ★★★★ ★★★★★ ★★★★★
Creational (Builder, Factory) ★★★★ ★★★★★ ★★★★
Behavioral (Strategy, Observer) ★★★★ ★★★★★ ★★★★★
Structural (Adapter, Facade) ★★★★ ★★★★★ ★★★★★

Ограничения и критика

GoF стареет по примерам, не по проблемам. Паттерн-omania («у нас Visitor на три класса») хуже отсутствия каталога. Многое из книги встроено в фреймворки — вы редко пишете Iterator вручную. Functional programming и immutability ставят под вопрос некоторые ООП-решения — но идея «изолировать изменение» остаётся.

Короткое сравнение. GoF — именованные формы структуры. «Чистый код» — локальная читаемость (и спорные правила). Рефactoring — как менять форму без смены поведения. DDD — какую модель строить; GoF — как связать объекты внутри модели. Не путайте слои.

Читайте GoF как справочник и язык, не как чеклист «23 галочки в Jira».

Кому читать

Стоит, если вы проектируете модули с нетривиальной связностью, интegraциями, меняющимися правилами; если на ревью не хватает общих имён; если читаете legacy с Factory и Observer.

Можно выборочно, если вы только на фреймворке «всё из коробки» — тогда достаточно знать 5–6 паттернов из статей и этой выжимки.

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

Что сделать сегодня

  1. В текущем модуле назовите одну проблему и проверьте, есть ли для неё один паттерн из GoF — или хватит простого рефакторинга.
  2. Найдите один switch по типу — рассмотрите Strategy только если вариантов станет больше или тесты требуют подмены.
  3. На ревью ИИ-диффа запретите новые иерархии классов без объяснения «какую проблему решаем».
  4. Откройте оригинал на одну главу (Strategy или Adapter) — сравните с вашим кодом, не с tutorial из 2010 года.