Паттерны поведения (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).