Паттерны проектирования
Паттерн проектирования — именованный шаблон решения повторяющейся задачи: описание сил, структуры и последствий. Его адаптируют, а не импортируют — и главное, за него платят. Валюта платежа всегда одна: косвенность. Лишний интерфейс, лишний уровень вызова, лишний прыжок при навигации по коду. Паттерн окупается, только когда решаемая им задача действительно есть.
На C#-собеседовании проверяют не знание каталога GoF, а умение эту цену увидеть — и заметить, что половину паттернов язык и фреймворк уже реализовали за вас. Strategy в C# — это чаще всего делегат Func<T>, а не интерфейс с классом на каждый алгоритм. Observer — это event, встроенный в язык вместе с подпиской и списком вызова. Конвейер middleware в ASP.NET Core — прямой Decorator над RequestDelegate. А рукописный Singleton со static-полем почти всегда проигрывает singleton'у в DI-контейнере: тот же один экземпляр, но подменяемый в тестах и освобождаемый при остановке. Разбор по слоям — ниже.
Карта темы
- Паттерн и три категории GoF — что такое паттерн, чем порождающие отличаются от структурных и поведенческих, и когда паттерн не нужен.
- Factory Method и Abstract Factory — один продукт через переопределяемый метод против согласованного семейства продуктов, и что вместо них даёт DI-контейнер.
- Builder и object initializer — почему инициализатор объекта — только сахар, что дают
requiredиinit, и где остаётся нуженBuild(). - Singleton против DI-контейнера — цена глобального
static-состояния, потокобезопасная ленивая инициализация иAddSingletonкак замена. - Strategy через делегаты — интерфейс с одним методом — это
Func<T>; что вы теряете и когда стоит вернуться к интерфейсу. - Observer и event —
eventкак multicast-делегат, синхронный вызов подписчиков и утечка из-за забытого-=. - Decorator и middleware — обёртка, сохраняющая интерфейс, и почему конвейер ASP.NET Core — это цепочка декораторов.
- Adapter и Facade — совместимость против упрощения, и тест на один вопрос, который их разводит.
- Mediator и MediatR — диспетчеризация по типу запроса,
IPipelineBehaviorкак декоратор и честная критика подхода.
Частые ошибки и ловушки
| Ошибка | Последствие |
|---|---|
| Считать паттерн готовым кодом для импорта | Структура копируется без контекста — вы платите косвенностью и ничего не выигрываете |
Называть любой static Create() абстрактной фабрикой | Abstract Factory — про согласованность семейства продуктов, а не про то, чтобы спрятать new |
| Ждать, что object initializer проверит обязательные поля | Это сахар над new без аргументов плюс присваивания — объект успевает пожить в невалидном состоянии |
Писать рукописный Singleton со static-полем | Глобальное изменяемое состояние: зависимость не видна в конструкторе и не подменяется в тесте |
Реализовывать double-checked locking вручную без volatile | Другой поток может увидеть ссылку на ещё не достроенный объект |
| Держать интерфейс с одним методом и класс на каждый алгоритм | Там, где Func<T> — это вся стратегия целиком, вы платите типом и классом за одно выражение |
Забыть -= на event | Издатель держит ссылку на подписчика — тот не собирается GC: классическая утечка |
| Считать, что обработчики события выполняются асинхронно | Они бегут синхронно в потоке, поднявшем событие, и исключение в одном обрывает остальные |
Забыть await next(context) в middleware | Остаток конвейера молча не выполняется — клиент получает пустой ответ |
Путать Decorator с Adapter | Декоратор сохраняет интерфейс, адаптер его меняет, facade — придумывает новый |
Строить Facade, пересылающий вызовы одному типу | Это адаптер или просто лишний слой |
Считать MediatR очередью или шиной сообщений | Это внутрипроцессный диспетчер: без буфера, без ретраев, вызов остаётся синхронным |
Значение для собеседований
Паттерны спрашивают почти на каждом C#-интервью — и почти всегда с подвохом. Ответ «Singleton гарантирует один экземпляр» формально верен и при этом провальный: собеседующий ждёт, что вы назовёте цену и скажете, чем это отличается от AddSingleton. Ценность кандидата здесь — критика, а не рецитация.
Что обычно проверяют:
- Что такое паттерн и чем порождающие отличаются от структурных и поведенческих.
- Разницу
Factory MethodиAbstract Factory— и что второй нужен ради согласованности семейства. - Почему рукописный
Singleton— обуза, и что даёт вместо него контейнер. - Как выразить
Strategyделегатом и что при этом теряется. - Почему
event— это готовыйObserver, и где здесь утечка памяти. - Что
Decoratorсохраняет интерфейс,Adapter— меняет,Facade— изобретает новый. - Что конвейер middleware в ASP.NET Core — это
Decorator, и почему порядок регистрации значим.
Типичный неверный ответ: «Паттерны нужны, чтобы код был правильным». Дальше разговор идёт про цену: каждый паттерн добавляет уровень косвенности, и без задачи, которая эту косвенность оправдывает, он делает код только хуже. Хороший кандидат сам называет случаи, когда паттерн не нужен.