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

Паттерны поведения (Behavioral Patterns)

Какую проблему решает эта категория

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

Если структурные паттерны про статическую композицию объектов, то поведенческие - про динамику: кто кому что "говорит", в каком порядке выполняются шаги алгоритма, как объект уведомляет других об изменениях, как запрос путешествует по системе. Это самая большая и разнородная категория GoF-паттернов, потому что "поведение" и "взаимодействие" - самая широкая часть проектирования.

Общая идея большинства паттернов этой группы - вынести изменяющуюся часть поведения в отдельный объект (стратегию, состояние, посетителя, команду) вместо того, чтобы "зашивать" её условными операторами в один класс.

Что объединяет паттерны из этой папки

Паттерн Ключевая идея
Strategy Инкапсулирует алгоритм и делает его взаимозаменяемым в рантайме
Template Method Фиксирует скелет алгоритма, отдавая отдельные шаги на откуп подклассам
Mediator Убирает связи "все со всеми", заменяя их связями "все с посредником"
Iterator Даёт единый способ обхода коллекции независимо от её внутреннего устройства
Observer Строит подписку на события: субъект уведомляет всех наблюдателей об изменениях
Memento Сохраняет закрытое состояние объекта для последующего восстановления
Visitor Добавляет новую операцию над иерархией классов, не трогая сами классы
Command Превращает запрос/действие в объект - с историей, очередью, отменой
State Меняет поведение объекта в зависимости от его внутреннего состояния
Chain of Responsibility Передаёт запрос по цепочке, пока кто-то из обработчиков не возьмёт его на себя

Когда об этой категории стоит вспомнить

  • Один и тот же метод содержит switch/if-else по типу или режиму работы, и этот список регулярно растёт → Strategy или State.
  • Несколько алгоритмов почти одинаковы, отличаются только парой шагов → Template Method.
  • Объекты общаются друг с другом напрямую, и граф связей между ними превращается в "спагетти" → Mediator.
  • Нужно обойти коллекцию, не раскрывая наружу её внутреннее хранилище (массив, дерево, связный список) → Iterator.
  • Одному событию должны отреагировать несколько независимых частей системы, о которых источник события не должен ничего знать → Observer.
  • Нужно реализовать undo/redo или откат, не раскрывая приватные поля сохраняемого объекта → Memento.
  • Нужно добавлять операции над разнородной иерархией классов чаще, чем добавлять новые классы в саму иерархию → Visitor.
  • Действие пользователя нужно поставить в очередь, залогировать, отменить (undo) или повторить (redo) → Command.
  • Заявка/сущность проходит через набор состояний, и в каждом состоянии одни и те же операции ведут себя по-разному → State.
  • Запрос может быть обработан одним из нескольких обработчиков, и заранее неизвестно, каким именно (middleware, валидаторы, обработка исключений) → Chain of Responsibility.

Важная оговорка для .NET

Часть поведенческих паттернов в C# выражается не через иерархии интерфейсов, а через делегаты и лямбды - это язык уже даёт готовый механизм "поведения как объекта первого класса":

  • Strategy почти всегда можно заменить полем типа Func<T, TResult> вместо интерфейса с одним методом;
  • Observer в базовом виде - это event и делегаты Action/EventHandler, а IObservable<T>/IObserver<T> и Reactive Extensions - более развитая версия того же паттерна;
  • Command нередко вырождается в Action/Func, если не нужны отмена и история;
  • Memento удобно сочетать с Command: команда инициирует действие, а снимок хранит состояние для надёжной отмены;
  • Template Method можно реализовать через методы расширения и делегаты вместо наследования.

ASP.NET Core middleware pipeline - это, по сути, живой пример Chain of Responsibility, а LINQ - обширный пример Iterator поверх IEnumerable<T> и ленивых вычислений (yield return).