Встраивание нескольких структур
Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy — урок 37 из 107.
Go позволяет встраивать несколько структур в одну структуру, объединяя поведение из разных источников. Это похоже на множественное наследование в других языках, но с использованием подхода композиции в Go.
type Logger struct{}
func (l Logger) Log(msg string) string {
return "LOG: " + msg
}
type Notifier struct{}
func (n Notifier) Notify(msg string) string {
return "NOTIFY: " + msg
}
type Service struct {
Name string
Logger
Notifier
}
Структура Service теперь имеет доступ к методам обоих встроенных типов:
func main() {
s := Service{Name: "OrderService"}
fmt.Println(s.Log("started")) // LOG: started
fmt.Println(s.Notify("new order")) // NOTIFY: new order
}
При встраивании нескольких структур может возникнуть конфликт имен, если два встраиваемых типа имеют методы с одинаковым именем. Go не разрешает эту неоднозначность автоматически:
type A struct{}
func (A) Greet() string { return "Hello from A" }
type B struct{}
func (B) Greet() string { return "Hello from B" }
type Combined struct {
A
B
}
func main() {
c := Combined{}
// c.Greet() - ошибка компиляции: неоднозначный селектор
fmt.Println(c.A.Greet()) // Hello from A
fmt.Println(c.B.Greet()) // Hello from B
}
При возникновении конфликтов вы должны явно указать, метод какого встроенного типа вы хотите вызвать, используя имя типа в качестве квалификатора.
Задание
ЛегкоДавайте создадим систему управления умным домом, которая объединяет несколько возможностей с помощью встраивания структур. Вы создадите устройство, которое может как управлять освещением, так и воспроизводить музыку, встраивая отдельные типы контроллеров.
Вы организуете свой код в трех файлах:
controllers.go: Создайте две независимые структуры контроллеров, которые предоставляют различные возможности:LightControllerс полемBrightness(int) и методомSetLight(level int) string, который возвращает"Light set to [level]%"AudioControllerс полемVolume(int) и методомSetVolume(level int) string, который возвращает"Volume set to [level]%"
Status() string— это создает конфликт имен, который вам нужно будет обработать.LightController.Status()должен возвращать"Brightness: [Brightness]%", аAudioController.Status()должен возвращать"Volume: [Volume]%".device.go: Создайте структуруSmartDeviceс полемName, которая встраивает какLightController, так иAudioController. Добавьте методFullStatus() string, который возвращает имя устройства вместе со статусами обоих контроллеров, разрешая неоднозначность путем явного вызова методаStatus()каждого встроенного типа.main.go: Считайте конфигурацию устройства из входных данных, создайтеSmartDevice, используйте продвинутые (promoted) методы для установки уровней освещенности и громкости, а затем выведите полный статус.
Будут предоставлены следующие входные данные:
- Строка 1: Имя устройства
- Строка 2: Начальный уровень яркости (целое число)
- Строка 3: Начальный уровень громкости (целое число)
- Строка 4: Новый уровень яркости для установки (целое число)
- Строка 5: Новый уровень громкости для установки (целое число)
Ваш метод FullStatus() должен возвращать:
[Name] - [LightController.Status()], [AudioController.Status()]Выведите результаты вызова SetLight и SetVolume (которые продвинуты из встроенных типов), затем выведите полный статус.
Например, для входных данных Living Room Hub, 50, 30, 75 и 60 ваш вывод должен быть:
Light set to 75%
Volume set to 60%
Living Room Hub - Brightness: 75%, Volume: 60%Обратите внимание, что SetLight и SetVolume доступны непосредственно в SmartDevice через продвижение методов, но Status() требует явного указания, так как оба встроенных типа имеют этот метод.
Шпаргалка
Go позволяет встраивать несколько структур в одну структуру, объединяя поведение из разных источников с помощью композиции:
type Logger struct{}
func (l Logger) Log(msg string) string {
return "LOG: " + msg
}
type Notifier struct{}
func (n Notifier) Notify(msg string) string {
return "NOTIFY: " + msg
}
type Service struct {
Name string
Logger
Notifier
}
Структура Service имеет доступ к методам обоих встроенных типов:
s := Service{Name: "OrderService"}
fmt.Println(s.Log("started")) // LOG: started
fmt.Println(s.Notify("new order")) // NOTIFY: new order
Конфликты имен возникают, когда встроенные типы имеют методы с одинаковыми именами. Вы должны явно указать, метод какого встроенного типа следует вызвать:
type A struct{}
func (A) Greet() string { return "Hello from A" }
type B struct{}
func (B) Greet() string { return "Hello from B" }
type Combined struct {
A
B
}
// c.Greet() - ошибка компиляции: неоднозначный селектор
c := Combined{}
fmt.Println(c.A.Greet()) // Hello from A
fmt.Println(c.B.Greet()) // Hello from B
Попробуйте сами
package main
import (
"fmt"
)
func main() {
// Чтение входных данных
var name string
fmt.Scanln(&name)
var initialBrightness int
fmt.Scanln(&initialBrightness)
var initialVolume int
fmt.Scanln(&initialVolume)
var newBrightness int
fmt.Scanln(&newBrightness)
var newVolume int
fmt.Scanln(&newVolume)
// TODO: Создайте SmartDevice с заданным именем и начальными значениями
// TODO: Используйте встроенный метод SetLight и выведите результат
// TODO: Используйте встроенный метод SetVolume и выведите результат
// TODO: Выведите полный статус, используя метод FullStatus()
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
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