Menu
Coddy logo textTech

Почему в Go нет наследования

Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy. Урок 34 из 107.

Традиционные объектно-ориентированные языки, такие как Java и C++, используют наследование, чтобы совместно использовать код между типами. Дочерний класс расширяет родительский класс, наследуя все его поля и методы. Go намеренно пропускает эту возможность.

Наследование создаёт тесную связанность между типами. Когда родительский класс изменяется, это влияет на все дочерние классы.

Глубокие иерархии наследования становятся трудными для понимания и сопровождения. «Проблема хрупкого базового класса» возникает, когда изменения в базовом классе неожиданно нарушают работу производных классов.

Создатели Go выбрали другой путь: композиция вместо наследования. Вместо того чтобы говорить «Собака is an Животное», Go поощряет вас говорить: «Собака has поведение, подобное поведению Животного». Этот тонкий сдвиг приводит к созданию более гибкого и удобного в сопровождении кода.

Go обеспечивает повторное использование кода с помощью двух механизмов, которые вы уже изучили:

  • Интерфейсы определяют контракты поведения без деталей реализации
  • Встраивание структур позволяет типам включать другие типы и повторно использовать их методы

Рассмотрим это сравнение. В традиционном ООП можно написать class Dog extends Animal. В Go вы встраиваете структуру Animal внутрь Dog и реализуете общие интерфейсы. В результате получается аналогичная функциональность при менее тесной связанности между типами.

В этой главе подробно рассматривается встраивание структур, показывая, как Go получает преимущества наследования без его недостатков.

challenge icon

Задание

Легко

Давайте создадим систему уведомлений, демонстрирующую подход Go с композицией вместо наследования. Вы создадите типы, которые используют общее поведение через интерфейсы и встраивание структур, а не через иерархии классов.

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

  • notifier.go: Определите интерфейс Notifier с одним методом Notify(message string) string. Также создайте структуру BaseNotifier с полем Name, которая будет встраиваться в другие типы. Добавьте в BaseNotifier метод с именем Format(message string) string, возвращающий сообщение с именем notifier в скобках в начале.
  • channels.go: Создайте два типа каналов уведомлений, которые встраивают BaseNotifier и реализуют интерфейс Notifier:
    • EmailNotifier с дополнительным полем Address
    • SMSNotifier с дополнительным полем Phone
    Метод Notify каждого типа должен использовать встроенный метод Format и включать в результат информацию, специфичную для соответствующего канала.
  • main.go: Создайте function с именем SendAlert, которая принимает любой Notifier и сообщение, а затем возвращает результат вызова Notify. Прочитайте сведения об уведомлениях из входных данных, создайте оба типа notifiers и продемонстрируйте, как их можно взаимозаменяемо использовать через интерфейс.

Будут предоставлены следующие входные данные:

  • Строка 1: Имя notifier для email
  • Строка 2: Email-адрес
  • Строка 3: Имя notifier для SMS
  • Строка 4: Номер телефона
  • Строка 5: Текст уведомления

Ваш метод Format в BaseNotifier должен возвращать:

[Name] message

Ваши методы Notify должны возвращать:

  • EmailNotifier: Email to [Address]: [formatted message]
  • SMSNotifier: SMS to [Phone]: [formatted message]

Например, если заданы Alerts, user@mail.com, Urgent, 555-1234 и Server down, результат должен быть таким:

Email to user@mail.com: [Alerts] Server down
SMS to 555-1234: [Urgent] Server down

Обратите внимание, как оба типа notifiers повторно используют метод Format из BaseNotifier благодаря встраиванию, при этом каждый предоставляет собственную реализацию Notify. function SendAlert работает с любым Notifier, не зная конкретного типа. Это композиция вместо наследования в действии.

Попробуйте сами

package main

import (
	"bufio"
	"fmt"
	"os"
)

// SendAlert принимает любой Notifier и сообщение, возвращает результат вызова Notify
// TODO: Реализуйте функцию SendAlert

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	
	// Читаем имя email-уведомителя
	scanner.Scan()
	emailName := scanner.Text()
	
	// Читаем адрес электронной почты
	scanner.Scan()
	emailAddress := scanner.Text()
	
	// Читаем имя SMS-уведомителя
	scanner.Scan()
	smsName := scanner.Text()
	
	// Читаем номер телефона
	scanner.Scan()
	phoneNumber := scanner.Text()
	
	// Читаем сообщение оповещения
	scanner.Scan()
	alertMessage := scanner.Text()
	
	// TODO: Создайте EmailNotifier с emailName и emailAddress
	
	// TODO: Создайте SMSNotifier с smsName и phoneNumber
	
	// TODO: Используйте SendAlert для отправки alertMessage через оба уведомителя
	// и выведите результаты
	
	// Подавить предупреждения о неиспользуемых переменных (удалите это, когда будете использовать переменные)
	_ = emailName
	_ = emailAddress
	_ = smsName
	_ = phoneNumber
	_ = alertMessage
}
quiz iconПроверьте себя

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

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

Потренируйтесь самостоятельно: Онлайн-компилятор Go