- Прямое создание структуры
- Синтаксис конструктора
- Контраст: прямое создание и конструктор
- Конструктор с дефолтными значениями
- Конструктор и инкапсуляция
- Конструкторы с разной логикой
- Когда без конструктора никак
- Итог
В 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. Они позволяют задать значения по умолчанию, встроить проверки, скрыть внутренние поля и предоставить разные варианты создания объектов. Такой подход делает код безопаснее, яснее и удобнее для сопровождения.
Самостоятельная работа
Закрепим идею конструкторов: зачем они нужны, как задавать значения по умолчанию и где прятать валидацию.
Задачи:
- Опишите структуру
Book
с полями:Title string
,PagesCnt int
. Напишите конструктор, который принимает название и количество страниц.
- Если на входе количество cnhfybw меньше нуля, то значение нормализуется к нулю.
Создайте несколько примеров в экземляров структуры и выведите значения, чтобы увидеть поведение.
Показать пример ответа
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
}
Дополнительные материалы
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.