Структуры в Go
Теория: Композиция структур
Когда мы говорим о композиции в Go, речь идет о возможности включать одну структуру внутрь другой. Это похоже на конструктор из кубиков: у нас есть готовые детали (структуры), и мы собираем из них более сложные объекты.
Вместо наследования, как в объектно-ориентированных языках, Go использует встраивание структур (embedding). Это более простой и прозрачный механизм: одна структура может содержать другую в качестве поля. Причем если это поле указано без имени (только тип), его методы и поля становятся доступными напрямую у внешней структуры.
Пример
Допустим, у нас есть система для описания заказов в интернет-магазине. У каждого заказа есть данные о клиенте и адресе доставки.
Мы могли бы в Order скопировать все поля: имя, email, город, улицу и так далее. Но это приведет к дублированию: если те же самые данные нужны в другой сущности (например, для профиля пользователя), придется копировать все снова.
Вместо этого мы выделяем отдельные структуры и используем композицию:
Теперь Order автоматически получает доступ к полям Customer и Address:
Мы можем обращаться к полям вложенных структур напрямую, будто они принадлежат Order. Это и есть "встраивание".
Зачем это нужно
- Избегаем дублирования кода. Если поля повторяются в нескольких местах, лучше вынести их в отдельную структуру и встроить.
- Логическая группировка. Поля собираются в отдельные сущности, которые отражают смысл задачи (например,
Customer,Address). - Расширяемость. Если завтра в адрес добавится индекс, нам не придется менять каждую структуру с адресом — достаточно изменить
Address. - Методы тоже наследуются. Если у Customer есть метод
ContactInfo(), то он доступен и уOrder.
Пример с методами
Метод ContactInfo определен у Customer, но мы можем вызвать его у Order напрямую, потому что Customer встроен.
Дополнительные примеры композиции структур
Логирование событий
В приложении часто нужно сохранять информацию о том, кто и когда сделал действие. Это удобно вынести в отдельную структуру:
Теперь и User, и Order автоматически имеют поля CreatedAt и CreatedBy.
Если бы мы не использовали композицию, то пришлось бы копировать поля CreatedAt и CreatedBy в каждую сущность — а таких сущностей в проекте может быть десятки.
Система прав доступа
Часто разные сущности в системе должны иметь одинаковую "обертку" с правами.
Теперь и File, и Project содержат набор прав, и мы можем проверять их одинаково:
Здесь композиция позволяет легко распространять общую модель поведения (права доступа) на разные сущности.
Работа с геоданными
Представим систему для доставки. У каждой сущности может быть координата: склад, курьер, заказ.
Теперь и склад, и курьер, и доставка имеют координаты, которые можно использовать в расчетах:
Если бы поля широты и долготы хранились отдельно в каждой структуре, обновлять или валидировать их было бы неудобно. А с общей структурой Location можно даже добавить метод DistanceTo и использовать его во всех местах.
Когда композицию использовать не стоит
Иногда разработчики "встраивают все подряд", и это вредит читаемости.
Дублирование без смысла
На первый взгляд нормально, но теперь у Car появилось поле Horsepower напрямую:
Проблема: читая код, можно подумать, что Horsepower — это характеристика Car, а не вложенного двигателя. Смысл потерялся.
Лучше явно оставить поле с именем:
Вложение ради «наследования»
Иногда новички пытаются использовать композицию как замену наследованию "животное → собака → пудель":
Теперь у Dog напрямую есть Name. Но если мы захотим сделать метод Speak() у Animal, а потом переопределить его у Dog, получится путаница с методами. Go специально не поддерживает наследование — композицию лучше применять для объединения свойств, а не для создания "иерархий классов".
Лучше так:
Просто у собаки есть Name, и это нормально. А если нужны разные сущности с именем, можно сделать интерфейс Named.
Конфликт имен
Теперь у Employee два Name, и при обращении employee.Name компилятор выдаст ошибку: «неоднозначность».
Лучше явно писать поля:
Теперь employee.Profile.Name и employee.Company.Name читаются без конфликтов.
Композиция — это один из ключевых приемов в Go. Она упрощает код, если использовать ее осознанно.
— Хорошо применять, когда есть повторяющиеся поля, которые удобно вынести в отдельную структуру, или когда нужно логически сгруппировать данные. Это избавляет от дублирования и делает модель предметной области чище. — Плохо применять, если из-за встраивания код становится двусмысленным, теряется явная связь между сущностями или появляется конфликт имен. В таких случаях лучше использовать обычные поля.
Главное правило простое: композиция должна подчеркивать смысл и структуру задачи, а не запутывать ее.


