> Какие паттерны проектирования применить для процессора с выбором стратегии обработки по типу события (Go)
Уровень: senior · Роль: backend · Язык: Go · Категория: Технические вопросы
Компании: Wildberries
Стек: Go
> Пример ответа
Короткий ответ
Для процессора с выбором стратегии обработки по типу события в Go оптимально сочетание паттернов Strategy и Registry (реализуемого через map[string]HandlerFunc). Strategy инкапсулирует алгоритмы обработки, Registry обеспечивает динамический выбор по типу события. Дополнительно можно использовать Factory Method для создания обработчиков и Pipeline для цепочек обработки. В Go это естественно выражается через интерфейсы и функции первого класса, без избыточной иерархии классов.
Подробное объяснение
Классическая постановка: есть поток событий разных типов, каждому типу соответствует своя логика обработки. Прямой switch/case - антипаттерн при росте числа типов: он нарушает open/closed principle и усложняет тестирование.
Strategy - базовый паттерн: определяем интерфейс Handler с методом Handle(ctx, event), каждая стратегия - отдельная структура или функция. Это даёт изоляцию логики, лёгкое добавление новых типов без изменения существующего кода.
Registry - дополнение к Strategy: храним соответствие eventType -> Handler в map. Выбор стратегии становится O(1) по типу. В Go это идиоматично: map[string]HandlerFunc - стандартный подход для роутеров (например, в net/http).
Factory Method - если создание обработчика требует зависимостей (БД, клиенты API), фабрика централизует их внедрение. В Go часто заменяется простой функцией-конструктором.
Pipeline - если обработка события - последовательность шагов (валидация, обогащение, сохранение), можно выстроить цепочку middleware. Но это отдельный сценарий, не обязательный для базового выбора стратегии.
Trade-off: Registry через map - проще и быстрее, но требует ручной регистрации. Альтернатива - рефлексия или кодогенерация, но они усложняют поддержку. Для senior-уровня важно показать понимание, когда хватает простого map, а когда нужен полноценный DI-контейнер.
На практике
В реальном Go-коде паттерн Strategy+Registry выглядит так:
- Определяем интерфейс обработчика - часто это просто
func(ctx context.Context, event Event) error. - Создаём registry - структуру с
map[string]HandlerFuncи методамиRegisterиGet. - Регистрируем обработчики в
init()или в явной функцииRegisterHandlers(registry). - В процессоре получаем обработчик по типу события, обрабатываем ошибку "неизвестный тип".
Важные детали:
- Регистрация должна быть потокобезопасной, если происходит в рантайме (обычно используется sync.RWMutex или регистрация только при старте).
- Обработка отсутствующего типа - отдельная стратегия (default handler), а не паника.
- Для тестирования удобно инжектить registry с моками.
Пример кода
GOtype Event struct {Type stringData []byte}type HandlerFunc func(ctx context.Context, ev Event) errortype Registry struct {mu sync.RWMutexhandlers map[string]HandlerFunc}func NewRegistry() *Registry {return &Registry{handlers: make(map[string]HandlerFunc)}}func (r *Registry) Register(eventType string, h HandlerFunc) {r.mu.Lock()defer r.mu.Unlock()r.handlers[eventType] = h}func (r *Registry) Get(eventType string) (HandlerFunc, bool) {r.mu.RLock()defer r.mu.RUnlock()h, ok := r.handlers[eventType]return h, ok}type Processor struct {registry *Registry}func (p *Processor) Process(ctx context.Context, ev Event) error {handler, ok := p.registry.Get(ev.Type)if !ok {return fmt.Errorf("unknown event type: %s", ev.Type)}return handler(ctx, ev)}// регистрацияfunc RegisterHandlers(r *Registry, db *sql.DB) {r.Register("user.created", func(ctx context.Context, ev Event) error {// обработка создания пользователяreturn nil})r.Register("order.paid", func(ctx context.Context, ev Event) error {// обработка оплаты заказаreturn nil})}
Как отвечать на собеседовании
Начни с краткого ответа: Strategy + Registry. Затем поясни, почему именно эти паттерны: Strategy даёт полиморфизм обработки, Registry - динамический выбор по типу. Упомяни, что в Go вместо классической иерархии классов используем функции и map - это идиоматично.
Покажи понимание trade-off: map - просто и быстро, но требует ручной регистрации; если типов сотни - рассмотри кодогенерацию или таблицы с метаданными. Упомяни, что switch/case допустим для 2-3 типов, но не масштабируется.
Если спросят про альтернативы - скажи про Visitor (если структура событий фиксирована, а операции меняются) и про Chain of Responsibility (если обработчики могут передавать событие дальше). Но подчеркни, что для типичного event processor Strategy+Registry - оптимальный выбор.
Заверши примером кода - покажи registry с map и обработку неизвестного типа. Это демонстрирует практический опыт.
Что проверяет интервьюер
- Понимание базовых паттернов GoF и умение адаптировать их к идиоматике Go.
- Способность видеть границы применимости: когда map достаточно, когда нужен DI.
- Умение проектировать расширяемую архитектуру без overengineering.
- Понимание конкурентности: потокобезопасность registry, обработка событий параллельно.
- Практические навыки: обработка ошибок, тестируемость, чистота API.
Типичные ошибки
- Предлагать классическую иерархию классов с интерфейсами для каждого обработчика - в Go это избыточно, достаточно функций.
- Забывать про потокобезопасность registry при регистрации в рантайме.
- Использовать рефлексию для выбора обработчика - это медленно и нечитаемо.
- Не обрабатывать случай неизвестного типа - паника или молчаливый пропуск события.
- Смешивать Strategy с цепочкой обязанностей: если обработчик может делегировать дальше - это уже другой паттерн, не усложняй базовое решение.
- Регистрировать обработчики в
init()без явного порядка - лучше явная функция инициализации.
> Похожие задачи по Go
Что такое файловые ссылки в Linux
Что такое NAT и как работает механизм NAT
Есть ли смысл объявлять мапу в конструкторе в Go вместо объявления переменной
Какие методы у мьютекса для блокировки на чтение и запись в Go
> Похожие задачи по backend
Что такое файловые ссылки в Linux
Что такое NAT и как работает механизм NAT
Есть ли смысл объявлять мапу в конструкторе в Go вместо объявления переменной
Какие методы у мьютекса для блокировки на чтение и запись в Go
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью