Menu
Coddy logo textTech

Встраивание нескольких структур

Часть раздела Объектно-ориентированное программирование путешествия по 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
}

При возникновении конфликтов вы должны явно указать, метод какого встроенного типа вы хотите вызвать, используя имя типа в качестве квалификатора.

challenge icon

Задание

Легко

Давайте создадим систему управления умным домом, которая объединяет несколько возможностей с помощью встраивания структур. Вы создадите устройство, которое может как управлять освещением, так и воспроизводить музыку, встраивая отдельные типы контроллеров.

Вы организуете свой код в трех файлах:

  • 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()
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование