> В чем разница между фабрикой и обычным конструктором (Go)

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

Компании: Wildberries

Стек: Go

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

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

Фабрика - это функция или метод, который инкапсулирует процесс создания объекта, тогда как конструктор - это специальный метод, вызываемый непосредственно при создании экземпляра через new или литерал. В Go нет конструкторов в классическом понимании, поэтому фабрики - основной паттерн для создания объектов с валидацией, инициализацией или выбором конкретной реализации. Фабрика даёт больше контроля: может возвращать интерфейс, ошибку, кешировать объекты или выбирать тип на основе параметров.

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

В языках с классами (Java, C++, Python) конструктор - это часть типа, вызываемая автоматически. Он не может возвращать ошибку (кроме исключений), не может вернуть другой тип или интерфейс, и его имя фиксировано. Фабрика - это отдельная функция, которая может:

  • возвращать интерфейс вместо конкретного типа
  • возвращать ошибку вместе с объектом
  • выполнять сложную логику инициализации (чтение конфигов, подключение к БД)
  • выбирать конкретную реализацию на основе параметров (например, тип хранилища)
  • кешировать или пулить объекты
  • скрывать неэкспортируемые типы

В Go конструкторов нет - есть только функции. Поэтому фабрика - это стандартный способ создания объектов, когда нужна дополнительная логика. Простой случай - литерал структуры &User{Name: "x"} - это аналог конструктора без логики. Если нужна валидация или дефолтные значения, используют фабрику:

GO
func NewUser(name string, age int) (*User, error) {
if name == "" {
return nil, errors.New("name is required")
}
return &User{name: name, age: age}, nil
}

Ключевое отличие: конструктор - это синтаксический механизм языка, фабрика - это паттерн проектирования. Фабрика может использовать конструктор внутри, но добавляет уровень абстракции.

На практике

В Go фабрики применяются повсеместно:

  • функции вида NewXxx - конвенция для конструкторов с логикой
  • возврат интерфейсов для сокрытия реализации (например, http.NewRequest возвращает *http.Request, а sql.Open возвращает *sql.DB)
  • паттерн sync.Pool - фабрика для переиспользования объектов
  • выбор реализации: NewStorage("postgres") вернёт Storage интерфейс с конкретной реализацией

Фабрика особенно полезна, когда:

  • создание объекта требует нескольких шагов
  • нужно вернуть ошибку при невалидных параметрах
  • тип объекта зависит от входных данных
  • нужно скрыть конструктор неэкспортируемого типа

При этом не стоит злоупотреблять: если создание объекта тривиально (простая инициализация полей), фабрика - избыточен. Литерал структуры читается проще.

Пример кода

GO
package main
import (
"errors"
"fmt"
)
type Storage interface {
Save(data string) error
}
type postgresStorage struct {
conn string
}
func (p *postgresStorage) Save(data string) error {
fmt.Printf("saving to postgres: %s\n", data)
return nil
}
type memoryStorage struct {
data []string
}
func (m *memoryStorage) Save(data string) error {
m.data = append(m.data, data)
fmt.Printf("saving to memory, total: %d\n", len(m.data))
return nil
}
// Фабрика возвращает интерфейс и ошибку
func NewStorage(kind string) (Storage, error) {
switch kind {
case "postgres":
return &postgresStorage{conn: "localhost:5432"}, nil
case "memory":
return &memoryStorage{}, nil
default:
return nil, errors.New("unknown storage kind")
}
}
// Фабрика с валидацией
type User struct {
name string
age int
}
func NewUser(name string, age int) (*User, error) {
if name == "" {
return nil, errors.New("name is required")
}
if age < 0 {
return nil, errors.New("age cannot be negative")
}
return &User{name: name, age: age}, nil
}
func main() {
storage, err := NewStorage("postgres")
if err != nil {
panic(err)
}
storage.Save("hello")
user, err := NewUser("Alice", 30)
if err != nil {
panic(err)
}
fmt.Printf("user: %+v\n", user)
}

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

Начни с определения: фабрика - это функция, которая создаёт объект, конструктор - синтаксическая конструкция языка. В Go конструкторов нет, поэтому фабрики - основной способ. Подчеркни ключевые преимущества: возврат интерфейса, обработка ошибок, выбор реализации. Приведи конкретный пример из практики - например, NewStorage из кода выше. Упомяни, что фабрика не всегда нужна: для простых структур достаточно литерала. Если спросят про производительность - отметь, что фабрика не добавляет накладных расходов, если не делать лишних аллокаций. Хорошо показать понимание trade-off: фабрика добавляет косвенность, но даёт гибкость.

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

Интервьюер проверяет:

  • понимание разницы между синтаксисом языка и паттерном проектирования
  • знание идиом Go: функции NewXxx, возврат интерфейсов, обработка ошибок
  • умение обосновать выбор: когда фабрика оправдана, а когда - избыточна
  • способность писать чистый код с валидацией и инкапсуляцией
  • понимание принципов SOLID, особенно open/closed principle - фабрика позволяет добавлять новые типы без изменения вызывающего кода

Ожидается, что кандидат уровня senior не просто перечислит отличия, а покажет практическое применение и осознанный выбор.

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

  • утверждение, что в Go есть конструкторы - это неверно, есть только функции
  • использование фабрики для каждой структуры без необходимости - оверинжиниринг
  • возврат конкретного типа вместо интерфейса, когда планируется несколько реализаций
  • игнорирование ошибок при создании объекта - фабрика должна возвращать error
  • создание фабрики, которая просто вызывает конструктор без добавления ценности
  • забывание про скрытие неэкспортируемых типов - фабрика позволяет вернуть интерфейс, не раскрывая реализацию

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

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