Зарегистрируйтесь, чтобы продолжить обучение

Конструкторы структур Структуры в Go

В Go нет специального ключевого слова constructor, как, например, в Java или C++. Но в реальной разработке нам часто нужно контролировать процесс создания объектов. Для этого используется соглашение: определяют обычную функцию, которая возвращает экземпляр структуры. Такие функции принято называть по схеме NewИмяТипа, например NewUser, NewOrder, NewConfig.

Прямое создание структуры

Начнем с примера:

type User struct {
    Name string
    Age  int
}

func main() {
    u := User{Name: "Иван", Age: 30}
    fmt.Println(u)
}

Это рабочий способ, но у него есть минусы:

  • Во-первых, логика размазывается по коду: если нужно проверять возраст, придется дублировать проверку в каждом месте.
  • Во-вторых, значения по умолчанию легко забыть. Допустим, новый пользователь должен иметь возраст 18 лет, но при ручном создании придется каждый раз явно писать Age: 18.
  • И наконец, поля могут изменяться напрямую, и это иногда ведет к некорректному состоянию.

Синтаксис конструктора

Чтобы избежать этих проблем, создают конструктор — функцию, которая собирает объект по правилам:

func NewTypeName(args...) *TypeName {
    return &TypeName{...}
}

Обычно такие функции возвращают указатель на структуру. Название принято начинать с New и указывать тип.

Контраст: прямое создание и конструктор

Если объект простой и никаких правил нет, можно писать:

u := User{Name: "Иван", Age: 30}

Но если нужно проверять данные или задавать дефолты, лучше использовать конструктор:

func NewUser(name string, age int) *User {
    if age < 0 {
        age = 0 // защита от некорректных данных
    }

    return &User{Name: name, Age: age}
}

u := NewUser("Иван", -5)
fmt.Println(u.Age) // 0

Теперь правила централизованы и не размазываются по коду.

Конструктор с дефолтными значениями

В реальном проекте часто нужно выставлять дефолты. Например, заказ всегда должен начинаться со статусом "new".

type Order struct {
    ID     int
    Items  []string
    Status string
}

func NewOrder(id int, items []string) *Order {
    return &Order{
        ID:     id,
        Items:  items,
        Status: "new",
    }

}

Так мы гарантируем, что бизнес-правило «новый заказ = статус new» всегда выполняется.

Конструктор и инкапсуляция

Особую ценность конструкторы показывают вместе с приватными полями. Поля, начинающиеся с маленькой буквы, недоступны вне пакета, и единственный способ создать объект — через конструктор.

type Account struct {
    id      int
    owner   string
    balance float64
}

func NewAccount(id int, owner string) *Account {
    return &Account{id: id, owner: owner, balance: 0}
}

Внешний код не может напрямую менять баланс, значит объект всегда будет в корректном состоянии.

Конструкторы с разной логикой

Иногда полезно иметь несколько конструкторов для разных сценариев:

// обычный пользователь
func NewUser(name string) *User {
    return &User{Name: name, Age: 18}
}

// админ
func NewAdmin(name string) *User {
    return &User{Name: name, Age: 30}
}

Теперь код читается как «создаем админа» или «создаем пользователя», а не как «ставим поле Age вручную».

Когда без конструктора никак

Сложные объекты почти всегда требуют конструктора. Например, конфигурация сервиса:

type Config struct {
    Path string
    Timeout time.Duration
    Limit int
}

func NewConfig(path string) *Config {
    return &Config{
        Path: path,
        Timeout: 30 * time.Second,
        Limit: 100,
    }
}

Если писать конфиг руками, легко забыть или перепутать значения. Конструктор снимает эту проблему и централизует дефолты.

Итог

В Go конструкторы — это не встроенный механизм языка, а соглашение о написании функций NewTypeName. Они позволяют задать значения по умолчанию, встроить проверки, скрыть внутренние поля и предоставить разные варианты создания объектов. Такой подход делает код безопаснее, яснее и удобнее для сопровождения.


Самостоятельная работа

Закрепим идею конструкторов: зачем они нужны, как задавать значения по умолчанию и где прятать валидацию.

Задачи:

  1. Опишите структуру Book с полями: Title string, PagesCnt int.
  2. Напишите конструктор, который принимает название и количество страниц.

    • Если на входе количество cnhfybw меньше нуля, то значение нормализуется к нулю.
  3. Создайте несколько примеров в экземляров структуры и выведите значения, чтобы увидеть поведение.

Показать пример ответа
package main

import "fmt"

type Book struct {
    Title    string
    PagesCnt int
}

func NewBook(title string, pagesCnt int) *Book {
    if pagesCnt < 0 {
        pagesCnt = 0
    }
    return &Book{Title: title, PagesCnt: pagesCnt}
}

func main() {
    a := NewBook("Go in Action", -50)
    b := NewBook("The Go Programming Language", 380)
    fmt.Println(a.Title, a.PagesCnt) // Go in Action 0
    fmt.Println(b.Title, b.PagesCnt) // The Go Programming Language 380
}

Дополнительные материалы

  1. Effective Go — Allocation with new
  2. Effective Go — Initialization
  3. Go Spec — Allocation (new)

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff