> С какими паттернами проектирования приходилось работать в Go? (Go)
Уровень: senior · Роль: backend · Язык: Go · Категория: Технические вопросы
Компании: Axel Pro
Стек: Go
> Пример ответа
Короткий ответ
В Go чаще всего использовал порождающие паттерны: functional options для конфигурации, singleton через sync.Once, пул объектов через sync.Pool. Из структурных - middleware-цепочки, adapter для интерфейсов, repository для работы с БД. Поведенческие: worker pool, pipeline, strategy через интерфейсы, observer через каналы. Важно понимать, что Go-идиоматика часто заменяет классические паттерны более простыми конструкциями: горутины вместо команд, каналы вместо observer, интерфейсы вместо абстрактных классов.
Подробное объяснение
Классические паттерны GoF в Go выглядят иначе из-за отсутствия наследования и наличия горутин. Основные группы:
- Порождающие: functional options (замена builder), singleton (sync.Once), factory через конструкторы, object pool (sync.Pool).
- Структурные: middleware (цепочка обработчиков), adapter (обёртка над внешними API), repository (абстракция над хранилищем), decorator через обёртки функций.
- Поведенческие: pipeline (каналы), worker pool (fan-out/fan-in), strategy (интерфейсы), state machine (явные переходы), observer (каналы вместо подписки).
Ключевое отличие - в Go паттерны часто реализуются через композицию и интерфейсы, а не через наследование. Например, strategy - это просто передача функции или интерфейса. Chain of responsibility - это middleware, где каждый обработчик решает, передавать ли управление дальше.
На практике
В реальных проектах чаще всего встречаются:
- Functional options для конфигурации сервисов и клиентов - это стандарт де-факто.
- Middleware для HTTP-серверов (логирование, auth, recovery).
- Repository для изоляции БД.
- Worker pool для обработки задач из очередей.
- Pipeline для потоковой обработки данных.
- Circuit breaker для устойчивости к сбоям внешних сервисов.
Также полезен паттерн "context propagation" - передача контекста через всю цепочку вызовов, хотя это скорее идиома, чем паттерн. Для кэширования часто используется singleflight - объединение конкурентных запросов к одному ключу.
Пример кода
Functional options:
GOtype Server struct {port inttimeout time.Duration}type Option func(*Server)func WithPort(p int) Option {return func(s *Server) { s.port = p }}func WithTimeout(t time.Duration) Option {return func(s *Server) { s.timeout = t }}func NewServer(opts ...Option) *Server {s := &Server{port: 8080, timeout: 5 * time.Second}for _, opt := range opts {opt(s)}return s}
Worker pool:
GOfunc workerPool(ctx context.Context, jobs <-chan Job, results chan<- Result, n int) {var wg sync.WaitGroupfor i := 0; i < n; i++ {wg.Add(1)go func() {defer wg.Done()for job := range jobs {select {case <-ctx.Done():returncase results <- process(job):}}}()}wg.Wait()close(results)}
Как отвечать на собеседовании
Структурируй ответ по группам паттернов, упоминай конкретные примеры из практики. Подчеркни, что в Go важнее идиоматика, чем слепое следование GoF. Расскажи, как паттерн решал конкретную проблему: например, middleware для сквозного логирования или singleflight для защиты от cache stampede. Упомяни trade-off: паттерны добавляют абстракцию, но в Go часто проще написать простую функцию.
Покажи понимание, когда паттерн избыточен: например, strategy через интерфейс с одним методом - это просто функция. Если спросят про конкретный паттерн - объясни его реализацию на Go и отличие от классической.
Что проверяет интервьюер
- Понимание идиоматичного Go, а не механического переноса паттернов из Java/C++.
- Умение выбирать паттерн под задачу, а не использовать все подряд.
- Знание конкурентных паттернов: worker pool, pipeline, fan-out/fan-in.
- Понимание композиции через интерфейсы вместо наследования.
- Способность объяснить trade-off между простотой и абстракцией.
Типичные ошибки
- Перечисление паттернов без примеров - выглядит как заучивание.
- Предложение классического observer через интерфейсы, когда в Go проще каналы.
- Использование singleton без необходимости - в Go чаще передают зависимость явно.
- Злоупотребление интерфейсами: если интерфейс имеет один метод, лучше передать функцию.
- Игнорирование sync.Once и sync.Pool - это важные инструменты.
- Утверждение, что паттерны в Go не нужны - это тоже ошибка, они нужны, но в адаптированном виде.
> Похожие задачи по Go
Почему важно валидировать входные данные
Как написать юнит-тест для функции, которая читает данные из базы и считает среднее?
Готов ли ты писать код сейчас?
На чем реализовывали поиск
> Похожие задачи по backend
Почему важно валидировать входные данные
Как написать юнит-тест для функции, которая читает данные из базы и считает среднее?
Готов ли ты писать код сейчас?
На чем реализовывали поиск
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью