Экспресс-курс · No. 04

Большинство из них ты уже не раз изобретал руками. Паттерн — это всего лишь имя: чтобы команда могла одним словом сказать то, на что ушёл бы абзац, и узнать приём раньше, чем сделает его криво.

Только суть · Один образ на паттерн · Примеры важнее UML

§ 01

Паттерн проектирования — это названное, переиспользуемое решение проблемы, которая всплывает в коде снова и снова. Это словарь, а не библиотека — его не устанавливают, его узнают.

Паттерн — это имя для приёма, который ты и так делаешь

Футболисты не изобретают «стенку» по ходу матча. У приёма есть имя, и назвать его быстрее, чем описывать весь манёвр.

Большинство паттернов — это то, что толковые разработчики и так изобретают руками. Имя превращает абзац объяснений в одно слово, общее для всей команды. Когда кто-то говорит «оберни это в Decorator», все мгновенно видят форму — и в этом весь смысл паттерна: общий словарь для приёмов, которые ты сделал бы в любом случае.

Паттерны находят, а не изобретают

Никто не изобретал арку. Строители раз за разом обнаруживали, что это самый прочный способ перекрыть пролёт, — и ей дали имя.

«Банда четырёх» не придумала 23 хитрых трюка; они каталогизировали решения, которые снова и снова всплывали в хорошем коде. Поэтому паттерн — не цель, в которую надо попасть, а форма, к которой ты приходишь сам, когда задача того требует. Если ты втискиваешь паттерн силой — скорее всего, у тебя ещё нет его проблемы.

Три семьи, три вопроса

На кухне есть инструменты для трёх задач: приготовить еду, выложить на тарелку и подать. Паттерны делятся так же.

Порождающие паттерны — про то, как объекты рождаются: кто их строит и как. Структурные — про то, как объекты складываются в большие формы. Поведенческие — про то, как объекты общаются и делят ответственность. Почти любой классический паттерн — это один из этих трёх ответов.

Паттерн, который тебе не нужен, — это просто сложность

Швейцарский нож в кармане — удобно. Привинтить все двадцать инструментов к входной двери — нет.

Любой паттерн добавляет лишнюю прослойку — слой между тобой и вещью. Это оправдано, когда покупает гибкость, которой ты реально воспользуешься, и чистая трата — когда нет. Главная ошибка не в незнании паттернов, а в том, чтобы тянуться за паттерном раньше, чем появилась проблема.

Паттерн — это сокращение для разговора. Используй его, чтобы тебя поняли, а не чтобы выглядеть умно.

§ 02

Как объекты появляются на свет — без россыпи new повсюду и без приваривания кода к конкретным классам.

Factory: проси вещь по имени, а не по рецепту

Кофемашина: ты жмёшь «латте» и получаешь его. Ты не мелешь, не взбиваешь и не наливаешь сам — и не обязан знать, как это устроено.

Фабрика выдаёт тебе объект, минуя прямой вызов конструктора, так что вызывающий код никогда не зависит от конкретного класса. Пример: createPaymentProvider("stripe") возвращает что-то, удовлетворяющее интерфейсу PaymentProvider — перейти на PayPal можно, поменяв фабрику, а не каждое место вызова. Это покупает свободу менять то, что строится, не трогая того, кто попросил.

Abstract Factory: целое семейство в едином стиле сразу

Покупаешь мебельный гарнитур — «викторианский» или «модерн» — где каждый предмет приходит в одном стиле, без разношёрстных стульев.

Когда объекты должны идти согласованными семьями, абстрактная фабрика строит весь набор так, чтобы они подходили друг к другу. Пример: UI-тулкит, который выдаёт согласованные кнопки, меню и диалоги под macOS и Windows из одной фабрики. Мощно — но это тяжёлая машинерия, оправданная, только когда семьи реальны.

Builder: собирай сложный объект шаг за шагом

Заказываешь бургер под себя — булка, котлета, без лука, двойной сыр — по одному выбору за раз, потом «готово».

Когда у объекта много необязательных частей, builder позволяет задавать их по кусочку, а не через монструозный конструктор на двенадцать аргументов. Пример: QueryBuilder().select("name").where("age > 18").limit(10).build(). Он меняет один сбивающий с толку конструктор на читаемую цепочку.

Singleton: ровно один, общий для всех

У страны один президент. Все, кто говорит «президент», имеют в виду одного и того же человека — их не двое.

Singleton гарантирует единственный экземпляр с одной глобальной точкой доступа. Пример: конфигурация или логгер, из которых читают все. Но обращайся осторожно: singleton — это глобальное состояние в красивом пальто, а глобальное состояние мешает тестировать и рассуждать. Часто единственный экземпляр, который ты передаёшь явно, лучше того, до которого дотягиваются через глобал.

Prototype: копируй заполненное, а не строй с нуля

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

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

Спрячь new. Чем меньше мест знает, как строится объект, тем свободнее ты его меняешь.

§ 03

Как объекты складываются в большие структуры — стыкуя несовместимые части и добавляя возможности без переписывания.

Adapter: переходник между двумя интерфейсами

Зарядка ноутбука за границей — устройство в порядке, розетка в порядке; нужен только маленький переходник между ними.

Adapter оборачивает один интерфейс так, чтобы он выглядел как другой, позволяя двум вещам, не созданным для совместной работы, сотрудничать. Пример: обернуть сторонний платёжный SDK так, чтобы он удовлетворял твоему интерфейсу PaymentProvider — твой код никогда не видит форму вендора. Так ты не даёшь чужому API протечь во весь твой код.

Decorator: добавляй начинку, не меняя пиццу

Простая пицца, потом сыр, потом грибы — каждый слой оборачивает предыдущий и что-то добавляет, и это всё ещё пицца.

Decorator оборачивает объект, добавляя поведение без правки оригинала и без наследования. Пример: обернуть поток данных, добавив сжатие, затем шифрование — каждый слой можно ставить и снимать. Так ты добавляешь возможности в сочетаниях, которые никто не обязан был предусмотреть.

Facade: одна простая стойка перед бардаком за кулисами

Консьерж в отеле — ты говоришь «нужно такси и столик на ужин», а он разруливает клубок звонков за стойкой.

Facade даёт простую, единую точку входа в сложную подсистему, пряча её движущиеся части. Пример: VideoConverter.convert(file, "mp4"), который втихую дирижирует кодеками, буферами и аудиопотоками под капотом. Он сжимает большую поверхность до одной двери, которая нужна большинству вызывающих.

Proxy: подставное лицо, контролирующее доступ

Ассистент знаменитости — ты не дотягиваешься до звезды напрямую; ассистент фильтрует, планирует, а иногда отвечает за неё.

Proxy встаёт перед объектом, контролируя доступ к нему — для ленивой загрузки, прав, кэширования или общения с чем-то удалённым. Пример: прокси картинки, который не грузит реальный файл, пока его не показывают; или прокси доступа, проверяющий права перед тем, как пробросить вызов. Тот же интерфейс, лишние ворота перед ним.

Composite: дерево и лист — на один манер

Папка хранит файлы и другие папки. «Посчитать размер» работает одинаково, спрашиваешь ли ты один файл или всё дерево.

Composite позволяет обращаться с одиночными объектами и группами объектов единообразно, так что ветка и лист делят один интерфейс. Пример: интерфейс, где и кнопка, и панель-полная-кнопок отвечают на render() и getSize(). Так рекурсивные структуры превращаются в то, с чем работаешь без особых случаев на каждом уровне.

Композиция вместо наследования: защёлкивай поведение в рантайме, а не замораживай его в дереве классов.

§ 04

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

Strategy: подмени алгоритм, оставь вызывающего

Приложение карт: та же поездка, но ты выбираешь «быстрее», «короче» или «без платных дорог», и маршрут строится иначе. Ты выбираешь стратегию; приложение просто следует ей.

Strategy делает алгоритм взаимозаменяемым в рантайме, так что вызывающий остаётся прежним, а способ меняется. Пример: оформление заказа, принимающее PricingStrategy — обычная, для участника или праздничная распродажа — вместо разрастающейся лестницы if. В современных языках это часто просто передача функции, а не целого класса — и в этом суть: паттерн — это идея, а не церемония.

Observer: подпишись — и узнаешь, когда изменится

Рассылка — ты подписываешься один раз, и каждый новый выпуск приходит к тебе. Издатель не звонит каждому читателю; он просто шлёт по списку.

Observer позволяет объектам подписаться на другой объект и получать уведомление, когда тот меняется, при этом он не знает, кто они. Пример: ячейка таблицы, обновляющая все зависящие от неё графики; UI-фреймворки, реагирующие на состояние; pub/sub повсюду. Это становой хребет событийного и реактивного кода.

Command: оберни запрос в объект

Заказной чек в ресторане — запрос «стол 5, два кофе» становится бумажкой, которую можно поставить в очередь, переставить, залогировать и даже порвать.

Command превращает действие в самостоятельный объект, так что его можно поставить в очередь, залогировать, отложить или отменить. Пример: каждая правка в редакторе — это команда, и именно это делает возможными undo/redo и макросы; очереди задач — это команды, ждущие своей очереди. Так действие становится тем, что можно сохранить и проиграть заново.

Chain of Responsibility: передавай дальше, пока кто-то не разберётся

Уровни поддержки — первая линия пробует, эскалирует специалисту, затем менеджеру, пока кто-то реально не решит вопрос.

Запрос идёт по цепочке обработчиков; каждый либо разбирается с ним, либо передаёт дальше. Пример: веб-middleware — аутентификация, потом логирование, потом rate-limit, потом сам маршрут — каждое звено делает свою часть или пробрасывает. Так отправитель отвязан от того, кто в итоге сделает работу.

Хорошие поведенческие паттерны дают двум кускам сотрудничать, не вшивая один в другой намертво.

§ 05

Как поведение объекта меняется с его состоянием, как ты идёшь по коллекциям и как чинишь скелет, оставляя один шаг открытым.

State: поведение, которое меняется с режимом

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

Паттерн State позволяет объекту менять поведение по мере смены внутреннего состояния, заменяя клубок if и switch на ясные состояния и переходы. Пример: заказ, который ведёт себя по-разному как pending, paid, shipped или cancelled, и каждое состояние знает только свои правила и то, чем оно может стать. Так каша из флагов превращается в аккуратный маленький автомат.

Iterator: иди по коллекции, не заглядывая внутрь

Кнопки «дальше» и «назад» на пульте — ты переключаешь каналы, не зная, как они хранятся.

Iterator даёт единый способ пройти коллекцию, не раскрывая, как она устроена — массив, дерево или связанный список. Пример: for (item of collection) работает одинаково над списком и над своей структурой. Этот настолько полезен, что язык обычно встраивает его внутрьfor..of, генераторы, __iter__ — что яснее всего показывает, как паттерн дорастает до фичи.

Template Method: фиксированный рецепт с одним пропуском

Рецепт, одинаковый каждый раз, — кроме «добавь специю на свой вкус» на четвёртом шаге. Скелет зафиксирован; один шаг — твой.

Template Method задаёт скелет операции и позволяет переопределять отдельные шаги, не меняя общей формы. Пример: базовый импортёр данных, который открывает, читает, разбирает (это заполняешь ты) и сохраняет, — а импортёры CSV и JSON подставляют только шаг разбора. Полезно, когда много потоков делят форму, но различаются в одном-двух местах.

И остальное — на одном дыхании

В ящике с инструментами есть те, за которыми тянешься раз в месяц, а не каждый день, — но знать об их существовании всё равно стоит.

Ещё горстка иногда оправдывает своё место: Mediator (диспетчерская, чтобы объекты говорили через один хаб, а не клубок прямых связей), Visitor (добавить новые операции к структуре, не правя её классы), Memento (захватить состояние объекта, чтобы восстановить его позже — точки сохранения, снимки для отмены). Знай имена; тянись за ними, только когда появилась именно эта проблема.

Когда паттерн становится ключевым словом языка — перестань писать паттерн и используй ключевое слово.

§ 06

Каталог — это десятилетия добытой потом мудрости, и одновременно продукт своего времени. Пользуйся им как сеньор, а не как чек-листом.

Многие паттерны теперь просто фичи языка

Раньше дорогу объясняли по ориентирам; теперь телефон сам ведёт. Навык не исчез — он впитался в инструмент.

Iterator — это for..of. Strategy — часто переданная функция. Decorator и Observer встроены в современные фреймворки. Когда язык даёт тебе форму бесплатно, писать полный паттерн руками — просто церемония. Идея по-прежнему важна; шаблонный код — обычно нет.

Паттерн-ит — реальная болезнь

Повар, вываливающий в одно блюдо всю полку специй, получает не более богатый вкус, а несъедобное месиво.

Классический запах переусложнения — тянуться за паттернами, чтобы выглядеть изощрённо: абстрактная фабрика на один продукт, Strategy на один алгоритм, который никогда не меняется, пять слоёв обёрток вокруг одного if. И сейчас хуже — AI-ассистент с радостью сгенерит «многоуровневую Abstract Factory» там, где должно быть три строки. Более простой код почти всегда — более сеньорский выбор.

Паттерны — это словарь, а не закон

Знание множества слов не делает хорошего писателя. Хорошего делает знание, какое слово — и когда промолчать.

Настоящая ценность каталога — общий язык: «давай поставим тут Facade» мгновенно доходит до любого, кто знает термин, и на команде это стоит немало. Но цель всегда — самый простой код, решающий задачу. Паттерн — это средство; назвать его — никогда не достижение.

Выучи все паттерны, чтобы их узнавать. Используй так мало, как только можешь себе позволить.

§ 07

Паттерны — это ответы. Мастерство — подобрать их к реальному вопросу и понять, когда правильный ответ — никакого паттерна.

Пусть паттерн вырастает из боли

Ты не берёшь гаечный ключ, пока не нашёл болт. Сначала видишь проблему — потом тянешься за инструментом, который к ней подходит.

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

Называй, только когда имя помогает

Ты не говоришь «я выполнил стенку» в обычной беседе. Но на поле, с партнёрами, это слово экономит целое предложение.

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

Прежде чем тянуться за паттерном
  • Какую повторяющуюся проблему он решает? Скажи одним предложением. - Есть ли у меня эта проблема сейчас, или я гадаю про будущее? - Справилась бы обычная функция или объект? Сначала попробуй так. - Не даёт ли мне язык это уже бесплатно? - Сделает ли имя код яснее для следующего человека — или просто наряднее? - Я рефакторю к нему или втискиваю заранее?
Признаки, что ты переусложнил
  • У тебя фабрика, которая строит ровно одну вещь. - Strategy с единственной стратегией, которая никогда не меняется. - Интерфейсов и обёрток больше, чем реального поведения. - Ты добавил паттерн, чтобы выглядеть сеньором, а не чтобы снять боль. - Новому читателю нужна UML-диаграмма, чтобы проследить три строки логики.
Признаки, что ты выбрал хорошо
  • Паттерн убрал растущую лестницу if/switch. - Ты можешь подменить реализацию, не трогая вызывающих. - Коллега узнал форму по имени сразу. - Обёртка окупается — ты реально пользуешься гибкостью. - С паттерном код проще, чем без него, а не просто хитрее.

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

Конец экспресс-курса · 7 глав · паттерны важнее UML

Дальше — первоисточник: «Design Patterns» «банды четырёх» и её более добрые современные спутники — «Head First Design Patterns», refactoring.guru, «Рефакторинг» Мартина Фаулера. Но читай их так, как учат шахматные дебюты: не чтобы зазубрить ходы, а чтобы узнавать позицию, когда она появится на твоей доске.