> С какими паттернами проектирования приходилось работать в 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:

GO
type Server struct {
port int
timeout 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:

GO
func workerPool(ctx context.Context, jobs <-chan Job, results chan<- Result, n int) {
var wg sync.WaitGroup
for i := 0; i < n; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for job := range jobs {
select {
case <-ctx.Done():
return
case 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 не нужны - это тоже ошибка, они нужны, но в адаптированном виде.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью