> Какие группы паттернов проектирования существуют (Go)

Уровень: senior · Роль: backend · Язык: Go · Категория: Технические вопросы

Компании: Wildberries

Стек: Go

> Пример ответа

Короткий ответ

Существует три классические группы паттернов проектирования: порождающие (creational), структурные (structural) и поведенческие (behavioral). Порождающие отвечают за создание объектов, структурные - за композицию классов и объектов, поведенческие - за взаимодействие и распределение ответственности между объектами. В Go часто используются функциональные опции, синглтон через sync.Once, стратегия через функции, а также конвейеры для обработки данных.

Подробное объяснение

Классификация паттернов основана на книге "Банды четырёх" (GoF) и остаётся актуальной для любого языка, включая Go.

Порождающие паттерны решают задачу гибкого создания объектов, скрывая логику инстанцирования. В Go это особенно важно из-за отсутствия конструкторов и наследования. Типичные представители: singleton, factory method, abstract factory, builder, prototype. В Go синглтон реализуется через sync.Once, а builder часто заменяется функциональными опциями.

Структурные паттерны определяют, как классы и объекты собираются в более крупные структуры, обеспечивая гибкость и расширяемость. Сюда входят adapter, facade, decorator, proxy, composite, bridge, flyweight. В Go из-за интерфейсов и композиции вместо наследования многие структурные паттерны упрощаются: adapter - это просто функция, преобразующая один интерфейс в другой, decorator - обёртка через замыкания.

Поведенческие паттерны описывают способы взаимодействия между объектами и распределение обязанностей. Это strategy, observer, command, iterator, state, template method, chain of responsibility, mediator, memento, visitor. В Go стратегия часто реализуется как поле-функция, observer - через каналы, command - через замыкания.

Важно понимать, что Go в силу своей простоты и отсутствия наследования требует адаптации классических паттернов. Многие из них становятся избыточными, а вместо них используются идиомы языка: goroutines, channels, context.Context, sync.Once, функциональные опции.

На практике

Для senior-позиции важно не просто перечислить группы, а показать понимание, когда паттерн уместен, а когда - нет. В Go часто встречается антипаттерн "over-engineering": попытка применить классический паттерн GoF там, где достаточно простой функции.

Практические рекомендации:

  • Порождающие паттерны в Go обычно сводятся к конструкторам вида NewX() и функциональным опциям.
  • Структурные паттерны часто заменяются интерфейсами и композицией - это естественный способ для Go.
  • Поведенческие паттерны хорошо ложатся на каналы и горутины, особенно observer и pipeline.
  • Для конкурентного доступа используйте sync.Once вместо синглтона с ручной синхронизацией.
  • Не создавайте иерархии классов - в Go нет наследования, используйте интерфейсы.

Пример кода

Порождающий паттерн - функциональные опции:

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
}

Структурный паттерн - adapter через функцию:

GO
type Handler interface {
Handle(ctx context.Context, req Request) Response
}
type HandlerFunc func(ctx context.Context, req Request) Response
func (f HandlerFunc) Handle(ctx context.Context, req Request) Response {
return f(ctx, req)
}

Поведенческий паттерн - strategy через поле-функцию:

GO
type Sorter struct {
compare func(a, b int) bool
}
func (s *Sorter) Sort(data []int) {
sort.Slice(data, func(i, j int) bool {
return s.compare(data[i], data[j])
})
}

Как отвечать на собеседовании

Начните с чёткого перечисления трёх групп и их назначения. Затем покажите, как паттерны адаптируются под Go: отсутствие наследования, интерфейсы, функции как значения, каналы. Приведите конкретные примеры из своей практики - это сильный сигнал для senior.

Структура ответа:

  1. Назовите группы и их цель.
  2. Приведите по одному примеру из каждой группы в Go.
  3. Объясните, когда паттерн полезен, а когда - избыточен.
  4. Упомяните идиомы Go, заменяющие классические паттерны.

Если интервьюер спрашивает про конкретный паттерн, сначала опишите проблему, которую он решает, затем реализацию в Go, затем trade-off.

Что проверяет интервьюер

Интервьюер оценивает:

  • Базовое знание классификации GoF.
  • Понимание, как особенности языка влияют на реализацию паттернов.
  • Умение отличать паттерн от антипаттерна.
  • Практический опыт: может ли кандидат привести реальный пример из кода.
  • Способность рассуждать о trade-off: когда паттерн оправдан, а когда усложняет код.
  • Глубину: senior должен знать, что в Go многие паттерны упрощаются или заменяются идиомами.

Типичные ошибки

  • Перечисление паттернов без объяснения, какую проблему они решают.
  • Механическое перенесение классических реализаций из Java/C++ в Go без учёта особенностей языка.
  • Утверждение, что паттерны - это серебряная пуля, и их нужно применять всегда.
  • Игнорирование идиом Go: функциональные опции, sync.Once, каналы.
  • Путаница между паттернами: например, путают strategy и state, adapter и facade.
  • Отсутствие примеров из реальной практики - для senior это критично.
  • Чрезмерное усложнение ответа: попытка рассказать все паттерны сразу вместо структурированного ответа.

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

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