Основы встраивания структур
Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy — урок 35 из 107.
Встраивание struct — это механизм Go для включения одного типа struct в другой. Вместо использования имени поля вы просто указываете тип, и Go продвигает поля встроенной struct во внешнюю структуру.
Вот базовый синтаксис:
type Address struct {
City string
Country string
}
type Person struct {
Name string
Address // встроенное — без имени поля
}
Когда вы встраиваете Address в Person, вы можете обращаться к встроенным полям напрямую через внешнюю структуру:
func main() {
p := Person{
Name: "Alice",
Address: Address{
City: "London",
Country: "UK",
},
}
fmt.Println(p.City) // London - прямой доступ
fmt.Println(p.Country) // UK - прямой доступ
fmt.Println(p.Address.City) // также работает
}
Обратите внимание, как p.City работает без явного написания p.Address.City. Go автоматически продвигает поля встроенной структуры, делая их доступными так, как если бы они принадлежали внешней структуре. Вы все еще можете получить к ним доступ через имя встроенного типа, когда это необходимо.
Это отличается от обычных вложенных структур, где для доступа к внутренним полям вы всегда должны использовать имя поля. Встраивание создает более плоский и удобный API, сохраняя при этом логическую группировку связанных данных.
Задание
ЛегкоДавайте создадим систему регистрации транспортных средств, которая использует встраивание структур для создания чистой и организованной модели данных. Вы отделите контактную информацию от данных о транспортном средстве, обеспечив при этом прямой доступ ко всем полям через встраивание.
Вы организуете свой код в двух файлах:
vehicle.go: Создайте две структуры, которые работают вместе через встраивание:- Структура
ContactInfoс полямиOwnerNameиPhone(оба типа string) - Структура
Vehicle, которая встраиваетContactInfo(без имени поля) и добавляет свои собственные поляMake,ModelиYear
SummaryдляVehicle, который возвращает отформатированную строку, содержащую всю информацию о транспортном средстве и контактах.- Структура
main.go: Считайте данные о транспортном средстве и владельце из входных данных, создайте экземплярVehicleсо встроеннымContactInfoи продемонстрируйте доступ к встроенным полям как напрямую, так и через имя встроенного типа. В конце выведите сводку о транспортном средстве.
Будут предоставлены следующие входные данные:
- Строка 1: Имя владельца
- Строка 2: Номер телефона
- Строка 3: Марка автомобиля
- Строка 4: Модель автомобиля
- Строка 5: Год (целое число)
Ваш метод Summary должен возвращать строку в следующем формате:
[Year] [Make] [Model] - Owner: [OwnerName], Phone: [Phone]В вашем основном файле продемонстрируйте продвижение полей (field promotion), выведя:
- Имя владельца, доступ к которому осуществляется напрямую через Vehicle (например,
v.OwnerName) - Телефон, доступ к которому осуществляется через встроенный тип (например,
v.ContactInfo.Phone) - Полную сводку из вашего метода
Summary
Например, для Alice Smith, 555-1234, Toyota, Camry и 2023 ваш вывод должен быть следующим:
Alice Smith
555-1234
2023 Toyota Camry - Owner: Alice Smith, Phone: 555-1234Обратите внимание, как работают оба способа доступа — прямой доступ через продвижение полей и явный доступ через имя встроенного типа — что дает вам гибкость при работе со встроенными данными.
Шпаргалка
Встраивание структур позволяет включать один тип структуры в другой без указания имени поля. Поля встроенной структуры переносятся (продвигаются) во внешнюю структуру, что делает их доступными напрямую.
Базовый синтаксис
type Address struct {
City string
Country string
}
type Person struct {
Name string
Address // embedded - no field name
}
Доступ к встроенным полям
Вы можете получить доступ к встроенным полям двумя способами:
p := Person{
Name: "Alice",
Address: Address{
City: "London",
Country: "UK",
},
}
// Direct access (field promotion)
fmt.Println(p.City) // London
// Through embedded type name
fmt.Println(p.Address.City) // London
Go автоматически продвигает поля встроенной структуры, обеспечивая прямой доступ к ним, как если бы они принадлежали внешней структуре. При необходимости вы все равно можете получить к ним доступ через имя встроенного типа.
Попробуйте сами
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Прочитать имя владельца
ownerName, _ := reader.ReadString('\n')
ownerName = strings.TrimSpace(ownerName)
// Прочитать номер телефона
phone, _ := reader.ReadString('\n')
phone = strings.TrimSpace(phone)
// Прочитать марку автомобиля
make, _ := reader.ReadString('\n')
make = strings.TrimSpace(make)
// Прочитать модель автомобиля
model, _ := reader.ReadString('\n')
model = strings.TrimSpace(model)
// Прочитать год выпуска
yearStr, _ := reader.ReadString('\n')
yearStr = strings.TrimSpace(yearStr)
year, _ := strconv.Atoi(yearStr)
// TODO: Создайте экземпляр Vehicle со встроенным ContactInfo
// Используйте значения, прочитанные из ввода выше
// TODO: Выведите имя владельца, обратившись к нему напрямую через Vehicle (продвижение полей)
// Пример: fmt.Println(v.OwnerName)
// TODO: Выведите номер телефона, обратившись к нему через имя встроенного типа
// Пример: fmt.Println(v.ContactInfo.Phone)
// TODO: Выведите полную сводку, используя метод Summary
// Пример: fmt.Println(v.Summary())
// Удалите эти строки, как только реализуете решение
_ = ownerName
_ = phone
_ = make
_ = model
_ = year
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП в Go
Внешние файлыРабочее пространство и модули GoПакеты и импортыЭкспортируемые и неэкспортируемые именаВведение в ООП в GoСтруктуры как классыОпределение методов структурПолучатели-указатели и получатели-значенияИнициализация структурФункции-конструкторыИтоги — Простой калькулятор4Интерфейсы
Введение в интерфейсыНеявная реализацияИнтерфейс как контрактПустой интерфейс (any)Утверждение типаПереключатель типовКомпозиция интерфейсовИнтерфейсы Stringer и ErrorПовторение: Калькулятор фигур7Инкапсуляция
Экспортируемые и неэкспортируемые поляИнкапсуляция на уровне пакетовГеттеры и сеттерыСокрытие информации в GoИтоги — Записи о студентах10Обобщения (Generics) (Go 1.18+)
Введение в GenericsПараметры типовОграничения типовОбобщенные структурыОбходной путь для обобщенных методовИтоги — Обобщенная коллекция13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн Abstract FactoryПаттерн ObserverПаттерн Strategy2Глубокое погружение в типы и структуры
Базовые и составные типыОпределение пользовательских типовТеги структурАнонимные структурыВложенные структурыНулевые значения и значения по умолчаниюПовторение — Контактная книга5Композиция вместо наследования
Почему в Go нет наследованияОсновы встраивания структурПродвижение методовВстраивание нескольких структурВстраивание против агрегацииЗатенение встроенных методовИтоги — Иерархия сотрудников8Обработка ошибок и ООП
Интерфейс errorПользовательские типы ошибокОбертывание ошибок (fmt.Errorf)Sentinel-ошибкиerrors.Is() и errors.As()Panic, Defer и RecoverИтоги — Парсер файлов11Стандартная библиотека и ООП
io.Reader и io.Writersort.InterfaceИнтерфейс fmt.Stringerencoding/json со структурамиИнтерфейс http.HandlerПовторение: модели REST API14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикMiddleware как Декоратор3Указатели и память
Основы указателей в GoУказатели на структурыПередача по значению и по ссылкеФункция new()Сборка мусора в GoПовторение: Конструктор связного списка6Полиморфизм в Go
Полиморфизм через интерфейсыУтиная типизация в GoПравила реализации интерфейсовПолиморфные коллекцииВнедрение зависимостейИтоги — Обработчик платежей9Конкурентность и ООП
Основы горутинКаналы и взаимодействиеБуферизованные и небуферизованные каналыОператор selectsync.Mutex и sync.RWMutexsync.WaitGroupПроектирование потокобезопасных структурПовторение — Worker Pool