Структуры - создаем свои типы
Структуры в Go - это способ группировки данных. Если вы знакомы с классами из ООП, структуры - это данные без наследования, но с методами.
Основы структур
Объявление
// Простая структура
type Person struct {
Name string
Age int
}
// С вложенными типами
type Employee struct {
Person Person
ID int
Salary float64
IsRemote bool
}
Создание экземпляров
// Нулевое значение
var p1 Person // Name: "", Age: 0
// Литерал структуры
p2 := Person{
Name: "Alice",
Age: 30,
}
// Позиционная инициализация (не рекомендуется)
p3 := Person{"Bob", 25}
// Частичная инициализация
p4 := Person{Name: "Carol"} // Age будет 0
Работа со структурами
Доступ к полям
person := Person{Name: "Alice", Age: 30}
// Чтение
fmt.Println(person.Name) // Alice
// Запись
person.Age = 31
// Через указатель (автоматическое разыменование)
p := &person
p.Age = 32 // эквивалентно (*p).Age = 32
Анонимные структуры
// Для одноразового использования
response := struct {
Status int
Message string
}{
Status: 200,
Message: "OK",
}
// В слайсах
points := []struct{ X, Y int }{
{10, 20},
{30, 40},
}
Методы
Value receiver
type Rectangle struct {
Width, Height float64
}
// Метод с value receiver
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
// Использование
rect := Rectangle{10, 5}
fmt.Println(rect.Area()) // 50
Pointer receiver
// Метод с pointer receiver (может изменять структуру)
func (r *Rectangle) Scale(factor float64) {
r.Width *= factor
r.Height *= factor
}
// Использование
rect := Rectangle{10, 5}
rect.Scale(2) // автоматически &rect
fmt.Println(rect) // {20 10}
<ComparisonTable data={{ headers: ["Тип receiver", "Когда использовать", "Особенности"], rows: [ ["Value", "Метод не меняет структуру, структура маленькая", "Копия при вызове"], ["Pointer", "Метод меняет структуру, структура большая, нужна единая сущность", "Работа с оригиналом"] ] }} />
Встраивание (Embedding)
Go использует композицию вместо наследования:
type Contact struct {
Email string
Phone string
}
type Person struct {
Name string
Age int
Contact // встраивание
}
// Использование
p := Person{
Name: "Alice",
Age: 30,
Contact: Contact{
Email: "alice@example.com",
Phone: "+1234567890",
},
}
// Прямой доступ к полям Contact
fmt.Println(p.Email) // alice@example.com
fmt.Println(p.Contact.Email) // тоже работает
Встраивание с методами
type Logger struct {
prefix string
}
func (l *Logger) Log(message string) {
fmt.Printf("[%s] %s\n", l.prefix, message)
}
type Service struct {
*Logger // встраивание указателя
name string
}
// Использование
svc := Service{
Logger: &Logger{prefix: "INFO"},
name: "UserService",
}
svc.Log("Service started") // метод Logger доступен напрямую
Теги структур
Теги - это метаданные для полей:
type User struct {
ID int `json:"id" db:"user_id"`
Name string `json:"name" db:"user_name"`
Email string `json:"email" db:"email" validate:"required,email"`
Password string `json:"-" db:"password"` // "-" исключает из JSON
CreatedAt time.Time `json:"created_at,omitempty"`
}
Использование с JSON
user := User{
ID: 1,
Name: "Alice",
Email: "alice@example.com",
}
// Маршалинг в JSON
data, _ := json.Marshal(user)
fmt.Println(string(data))
// {"id":1,"name":"Alice","email":"alice@example.com"}
// Анмаршалинг из JSON
jsonStr := `{"id":2,"name":"Bob","email":"bob@example.com"}`
var newUser User
json.Unmarshal([]byte(jsonStr), &newUser)
Сравнение структур
type Point struct {
X, Y int
}
p1 := Point{1, 2}
p2 := Point{1, 2}
p3 := Point{2, 3}
fmt.Println(p1 == p2) // true
fmt.Println(p1 == p3) // false
// Структуры можно использовать как ключи в map
m := make(map[Point]string)
m[p1] = "A"
Паттерны и best practices
Конструкторы
type Server struct {
host string
port int
tls bool
}
// Конструктор
func NewServer(host string, port int) *Server {
return &Server{
host: host,
port: port,
tls: port == 443,
}
}
// С опциональными параметрами
type ServerOption func(*Server)
func WithTLS(enabled bool) ServerOption {
return func(s *Server) {
s.tls = enabled
}
}
func NewServerWithOptions(host string, port int, opts ...ServerOption) *Server {
s := &Server{host: host, port: port}
for _, opt := range opts {
opt(s)
}
return s
}
Builder pattern
type QueryBuilder struct {
table string
columns []string
where []string
limit int
}
func (q *QueryBuilder) Select(columns ...string) *QueryBuilder {
q.columns = columns
return q
}
func (q *QueryBuilder) From(table string) *QueryBuilder {
q.table = table
return q
}
func (q *QueryBuilder) Where(condition string) *QueryBuilder {
q.where = append(q.where, condition)
return q
}
func (q *QueryBuilder) Limit(n int) *QueryBuilder {
q.limit = n
return q
}
func (q *QueryBuilder) Build() string {
// построение SQL запроса
return fmt.Sprintf("SELECT %s FROM %s WHERE %s LIMIT %d",
strings.Join(q.columns, ", "),
q.table,
strings.Join(q.where, " AND "),
q.limit)
}
// Использование
query := (&QueryBuilder{}).
Select("id", "name").
From("users").
Where("age > 18").
Limit(10).
Build()
Validation
type Email string
func (e Email) Validate() error {
if !strings.Contains(string(e), "@") {
return errors.New("invalid email")
}
return nil
}
type User struct {
Name string
Email Email
}
func (u *User) Validate() error {
if u.Name == "" {
return errors.New("name is required")
}
return u.Email.Validate()
}
Производительность
Размер и выравнивание
type Inefficient struct {
a bool // 1 байт + 7 padding
b int64 // 8 байт
c bool // 1 байт + 7 padding
} // Итого: 24 байта
type Efficient struct {
b int64 // 8 байт
a bool // 1 байт
c bool // 1 байт + 6 padding
} // Итого: 16 байт
fmt.Println(unsafe.Sizeof(Inefficient{})) // 24
fmt.Println(unsafe.Sizeof(Efficient{})) // 16
Копирование vs указатели
type Large struct {
data [1000]int
}
// Плохо: копирует 8KB при каждом вызове
func processValue(l Large) {
// ...
}
// Хорошо: передает только указатель (8 байт)
func processPointer(l *Large) {
// ...
}
Практический пример: REST API модель
type APIResponse struct {
Success bool `json:"success"`
Data interface{} `json:"data,omitempty"`
Error *APIError `json:"error,omitempty"`
Meta *Meta `json:"meta,omitempty"`
}
type APIError struct {
Code string `json:"code"`
Message string `json:"message"`
Details map[string]interface{} `json:"details,omitempty"`
}
type Meta struct {
Page int `json:"page"`
PerPage int `json:"per_page"`
Total int `json:"total"`
TotalPages int `json:"total_pages"`
}
// Конструкторы для удобства
func SuccessResponse(data interface{}) APIResponse {
return APIResponse{
Success: true,
Data: data,
}
}
func ErrorResponse(code, message string) APIResponse {
return APIResponse{
Success: false,
Error: &APIError{
Code: code,
Message: message,
},
}
}
Итоги
- Структуры группируют связанные данные
- Методы добавляют поведение
- Композиция через встраивание вместо наследования
- Теги для метаданных
- Value vs Pointer receivers
В следующем уроке изучим интерфейсы - мощный инструмент абстракции!
Типичная ошибка
Хранить всё строками «чтобы проще». Потом всё равно придётся парсить - лучше сразу правильные типы.