Menu
Coddy logo textTech

Затенение встроенных методов

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

Иногда вам нужно, чтобы внешняя структура предоставляла собственную реализацию метода, который уже есть у встроенного типа. Это называется затенением, и это позволяет переопределять продвинутые методы собственным поведением.

Когда вы определяете метод во внешней структуре с тем же именем, что и у встроенного метода, внешний метод имеет приоритет:

type Animal struct {
    Name string
}

func (a Animal) Speak() string {
    return "Some sound"
}

type Dog struct {
    Animal
    Breed string
}

func (d Dog) Speak() string {
    return "Woof!"
}

Теперь, когда вы вызываете Speak() у Dog, используется внешний метод:

func main() {
    d := Dog{Animal: Animal{Name: "Rex"}, Breed: "Labrador"}
    
    fmt.Println(d.Speak())        // Woof! — метод Dog
    fmt.Println(d.Animal.Speak()) // Some sound — метод Animal
}

Встроенный метод никуда не делся. Вы по-прежнему можете обращаться к нему явно через имя встроенного типа. Этот паттерн полезен, когда вы хотите настроить поведение, сохраняя при этом доступ к исходной реализации, возможно, чтобы вызвать её из вашего переопределяющего метода:

func (d Dog) Speak() string {
    return d.Animal.Speak() + " (but actually Woof!)"
}

Затенение дает вам контроль над тем, какое поведение будет доступно, сохраняя при этом доступ к оригиналу через явное указание области видимости.

challenge icon

Задание

Легко

Давайте создадим систему уведомлений, в которой различные каналы уведомлений могут настраивать способ форматирования и доставки сообщений. Вы будете использовать затенение методов (method shadowing), чтобы позволить конкретным каналам переопределять поведение по умолчанию, сохраняя при этом возможность доступа к исходной реализации при необходимости.

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

  • base.go: Создайте структуру BaseNotification с полями Title и Priority (оба типа string). Добавьте ей метод Format() string, который возвращает уведомление в стандартном формате: [Priority] Title.
  • channels.go: Создайте два типа каналов уведомлений, которые встраивают BaseNotification и затеняют метод Format своими собственными реализациями:
    • EmailNotification с полем Recipient — его метод Format() должен возвращать Email to [Recipient]: [Priority] Title
    • SlackNotification с полем Channel — его метод Format() должен вызывать встроенный метод BaseNotification.Format() и добавлять #[Channel]: перед результатом
  • main.go: Считайте данные уведомления из входного потока, создайте оба типа уведомлений и продемонстрируйте затенение, выведя на печать:
    1. Отформатированный вывод EmailNotification (используя его затененный метод)
    2. Базовый формат EmailNotification (явно вызывая встроенный метод)
    3. Отформатированный вывод SlackNotification (который внутри использует базовый метод)

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

  • Строка 1: Заголовок уведомления
  • Строка 2: Уровень приоритета
  • Строка 3: Получатель электронной почты
  • Строка 4: Название канала Slack

Например, для входных данных Server Alert, HIGH, admin@company.com и ops-alerts ваш вывод должен быть следующим:

Email to admin@company.com: HIGH Server Alert
[HIGH] Server Alert
#ops-alerts: [HIGH] Server Alert

Обратите внимание, как EmailNotification полностью заменяет базовое форматирование, в то время как SlackNotification расширяет его, вызывая оригинальный метод и добавляя контекст канала. Оба подхода демонстрируют различные способы эффективного использования затенения методов.

Шпаргалка

Когда вы определяете метод во внешней структуре с тем же именем, что и метод встроенного типа, внешний метод затеняет (переопределяет) встроенный метод:

type Animal struct {
    Name string
}

func (a Animal) Speak() string {
    return "Some sound"
}

type Dog struct {
    Animal
    Breed string
}

func (d Dog) Speak() string {
    return "Woof!"
}

При вызове метода внешней структуры приоритет имеет внешний метод:

d := Dog{Animal: Animal{Name: "Rex"}, Breed: "Labrador"}

fmt.Println(d.Speak())        // Woof! - метод Dog
fmt.Println(d.Animal.Speak()) // Some sound - метод Animal

Встроенный метод остается доступным через явное указание имени встроенного типа. Вы можете вызвать оригинальную реализацию из переопределяющего метода:

func (d Dog) Speak() string {
    return d.Animal.Speak() + " (but actually Woof!)"
}

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

package main

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

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	
	// Прочитать заголовок уведомления
	scanner.Scan()
	title := scanner.Text()
	
	// Прочитать уровень приоритета
	scanner.Scan()
	priority := scanner.Text()
	
	// Прочитать получателя электронной почты
	scanner.Scan()
	recipient := scanner.Text()
	
	// Прочитать название канала Slack
	scanner.Scan()
	channel := scanner.Text()
	
	// TODO: Создать EmailNotification с базовым уведомлением, заголовком, приоритетом и получателем
	
	// TODO: Создать SlackNotification с базовым уведомлением, заголовком, приоритетом и каналом
	
	// TODO: Вывести отформатированный вывод EmailNotification (используя его переопределенный метод)
	
	// TODO: Вывести базовый формат EmailNotification (явно вызывая встроенный метод)
	
	// TODO: Вывести отформатированный вывод SlackNotification
	
	fmt.Println() // Заполнитель - удалить при реализации
}
quiz iconПроверьте себя

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

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