Уроки модуля (5/5)
Шаблон конструктора `New...`
В Go нет ключевого слова constructor. Конвенция — это фабрика
функция, которая возвращает правильно инициализированное значение или указатель. Автор
имя обычно называется NewT (или NewXxx).
Базовый узор: NewT
type User struct {
Name string
Age int
}
func NewUser(name string, age int) *User {
return &User{Name: name, Age: age}
}
u := NewUser("Ada", 36)
fmt.Println(u.Name)Преимущества по сравнению с прямым литералом &User{...}:
- Вы централизуете проверку и настройки по умолчанию. — Вы можете иметь неэкспортированные поля и заполнять их только из конструктора.
- Вы можете изменить реализацию, не ломая вызывающие программы.
Конструктор с проверкой: (*T, ошибка)
Если инициализация может завершиться неудачей, верните (*T, error):
import "errors"
func NewUser(name string, age int) (*User, error) {
if name == "" {
return nil, errors.New("nome obbligatorio")
}
if age < 0 {
return nil, errors.New("eta non valida")
}
return &User{Name: name, Age: age}, nil
}
u, err := NewUser("Ada", 36)
if err != nil {
// handle...
}Это идиоматический шаблон для любого конструктора с предварительные условия: соединение с БД, дескриптор файла, конфигурация сервера и т. д.
Несколько конструкторов
Если у вас есть несколько «способов» создания, используйте описательные имена:
func NewUser(name string) *User { ... }
func NewUserFromJSON(data []byte) (*User, error) { ... }
func NewAdmin() *User { ... }Никакой перегрузки в Go: имя устраняет неоднозначность.
Функциональные возможности (расширенный обзор)
Если имеется много необязательных параметров, идиоматический шаблон «функциональные возможности»:
type Option func(*Server)
func WithPort(p int) Option {
return func(s *Server) { s.Port = p }
}
func WithTLS(cert, key string) Option {
return func(s *Server) { s.Cert, s.Key = cert, key }
}
func NewServer(opts ...Option) *Server {
s := &Server{Port: 8080} // default
for _, opt := range opts {
opt(s)
}
return s
}
s := NewServer(WithPort(9000), WithTLS("c.pem", "k.pem"))Он более многословен, чем литерал, но хорошо масштабируется со многими дополнительными параметрами.
параметры и сохраняет обратную совместимость (добавление нового Option
не прерывает существующие абоненты).
Когда вам НЕ нужен конструктор
Если нулевое значение типа уже полезно, позвольте вызывающему объекту использовать
прямой буквал. Канонический пример: sync.Mutex не имеет конструктора,
var m sync.Mutex — это уже готовый к использованию мьютекс. То же самое для bytes.Buffer.
Правило такое: «Сделайте нулевое значение полезным». Если можешь, нет обязательный конструктор.
Попробуйте это
Напишите фабричную функцию NewUser(name string, age int) *User, которая возвращает &User{Name: name, Age: age}.
Показать подсказку
Используйте литерал `&User{...}` для возврата указателя.
Решение доступно после 3 попыток
Превратить NewUser в (строку имени) (*User, error): если name == '' вернуть ноль, error.New('nome obbligatorio').
Показать подсказку
Если есть предварительные условия, верните (*T, ошибка) и верните ноль + ошибка в случае неудачи.
Решение доступно после 3 попыток
Каково идиоматическое соглашение для конструкторов в Go?
// Which signature is more idiomatic?Резюме
- Нет
constructor: используйте функциюNewT(илиNewвнутри одноименного пакета). - Верните
*Tдля простого случая;(*T, error)с предварительными условиями или проверкой. - При ошибке: вернуть
nilв качестве указателя и пояснительную ошибку в качестве второго значения. - Никаких перегрузок; используйте описательные имена (
NewUserFromJSON,NewAdmin). — «Сделайте нулевое значение полезным»: если можете, то конструктор не нужен. - Расширенный шаблон для многих дополнительных параметров: функциональные параметры.