Shadowing de Métodos Incorporados
Parte da seção Programação Orientada a Objetos do Journey de GO da Coddy — lição 39 de 107.
Às vezes, você precisa que a struct externa forneça sua própria implementação de um método que o tipo incorporado já possui. Isso é chamado de shadowing, e permite que você sobrescreva métodos promovidos com um comportamento personalizado.
Quando você define um método na struct externa com o mesmo nome de um método incorporado, o método externo tem precedência:
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!"
}
Agora, quando você chama Speak() em um Dog, o método externo é usado:
func main() {
d := Dog{Animal: Animal{Name: "Rex"}, Breed: "Labrador"}
fmt.Println(d.Speak()) // Woof! - método do Dog
fmt.Println(d.Animal.Speak()) // Some sound - método do Animal
}
O método incorporado não desapareceu. Você ainda pode acessá-lo explicitamente através do nome do tipo incorporado. Este padrão é útil quando você deseja personalizar o comportamento mantendo a implementação original disponível, talvez para chamá-la de dentro do seu método de sobreposição:
func (d Dog) Speak() string {
return d.Animal.Speak() + " (but actually Woof!)"
}
O sombreamento (shadowing) oferece controle sobre qual comportamento é exposto, mantendo o acesso ao original por meio de qualificação explícita.
Desafio
FácilVamos construir um sistema de notificação onde diferentes canais de notificação podem personalizar como formatam e entregam mensagens. Você usará o sombreamento de métodos (method shadowing) para permitir que canais específicos substituam o comportamento padrão, mantendo a capacidade de acessar a implementação original quando necessário.
Você organizará seu código em três arquivos:
base.go: Crie uma structBaseNotificationcom os camposTitleePriority(ambos strings). Adicione a ela um métodoFormat() stringque retorna a notificação em um formato padrão:"[Priority] Title".channels.go: Crie dois tipos de canais de notificação que incorporam (embed)BaseNotificatione sombreiam o métodoFormatcom suas próprias implementações:EmailNotificationcom um campoRecipient—seuFormat()deve retornar"Email to [Recipient]: [Priority] Title"SlackNotificationcom um campoChannel—seuFormat()deve chamar o métodoBaseNotification.Format()incorporado e prefixar"#[Channel]: "ao resultado
main.go: Leia os detalhes da notificação da entrada, crie ambos os tipos de notificações e demonstre o sombreamento imprimindo:- A saída formatada da
EmailNotification(usando seu método sombreado) - O formato base da
EmailNotification(chamando explicitamente o método incorporado) - A saída formatada da
SlackNotification(que usa internamente o método base)
- A saída formatada da
As seguintes entradas serão fornecidas:
- Linha 1: Título da notificação
- Linha 2: Nível de prioridade
- Linha 3: Destinatário do e-mail
- Linha 4: Nome do canal do Slack
Por exemplo, dados "Server Alert", "HIGH", "admin@company.com" e "ops-alerts", sua saída deve ser:
Email to admin@company.com: HIGH Server Alert
[HIGH] Server Alert
#ops-alerts: [HIGH] Server AlertObserve como a EmailNotification substitui completamente a formatação base, enquanto a SlackNotification a aprimora chamando o método original e adicionando o contexto do canal. Ambas as abordagens demonstram diferentes maneiras de usar o sombreamento de métodos de forma eficaz.
Folha de consulta
Quando você define um método em uma struct externa com o mesmo nome de um método de um tipo incorporado, o método externo sombreia (sobrescreve) o método incorporado:
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!"
}
Ao chamar o método na struct externa, o método externo tem precedência:
d := Dog{Animal: Animal{Name: "Rex"}, Breed: "Labrador"}
fmt.Println(d.Speak()) // Woof! - método de Dog
fmt.Println(d.Animal.Speak()) // Some sound - método de Animal
O método incorporado permanece acessível através de qualificação explícita usando o nome do tipo incorporado. Você pode chamar a implementação original de dentro do método de sobreposição:
func (d Dog) Speak() string {
return d.Animal.Speak() + " (but actually Woof!)"
}
Experimente você mesmo
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Ler o título da notificação
scanner.Scan()
title := scanner.Text()
// Ler o nível de prioridade
scanner.Scan()
priority := scanner.Text()
// Ler o destinatário do e-mail
scanner.Scan()
recipient := scanner.Text()
// Ler o nome do canal do Slack
scanner.Scan()
channel := scanner.Text()
// TODO: Criar uma EmailNotification com a notificação base, título, prioridade e destinatário
// TODO: Criar uma SlackNotification com a notificação base, título, prioridade e canal
// TODO: Imprimir a saída formatada da EmailNotification (usando seu método sombreado)
// TODO: Imprimir o formato base da EmailNotification (chamando explicitamente o método incorporado)
// TODO: Imprimir a saída formatada da SlackNotification
fmt.Println() // Espaço reservado - remova ao implementar
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Programação Orientada a Objetos
1Fundamentos de OOP em Go
Arquivos ExternosGo Workspace e MódulosPacotes e ImportaçõesNomes Exportados vs Não ExportadosIntrodução a OOP em GoStructs como ClassesDefinindo Métodos em StructsRecebedores de Ponteiro vs ValorInicialização de StructsFunções ConstrutorasRecapitulação - Calculadora Simples4Interfaces
Introdução a InterfacesImplementação ImplícitaInterface como ContratoInterface Vazia (any)Type AssertionType SwitchComposição de InterfacesInterfaces Stringer & ErrorRecapitulação - Calculadora de Formas7Encapsulamento
Campos Exportados vs Não ExportadosEncapsulamento em Nível de PacoteMétodos Getter e SetterOcultação de Informação em GoRecapitulação - Registros de Estudantes10Generics (Go 1.18+)
Introdução a GenericsParâmetros de TipoRestrições de TipoStructs GenéricasSolução Alternativa para Métodos GenéricosRecapitulação - Coleção Genérica2Mergulho Profundo em Tipos e Structs
Tipos Básicos e CompostosDefinições de Tipos PersonalizadosStruct TagsStructs AnônimasStructs AninhadasZero Values e PadrõesRecapitulação - Agenda de Contatos5Composição em vez de Herança
Por que Go não possui HerançaFundamentos de Incorporação de StructsPromoção de MétodosIncorporando Múltiplas StructsIncorporação vs AgregaçãoShadowing de Métodos IncorporadosRecapitulação - Hierarquia de Funcionários8Tratamento de Erros e OOP
A Interface errorTipos de Erro PersonalizadosError Wrapping (fmt.Errorf)Erros Sentinelaerrors.Is() e errors.As()Panic, Defer e RecoverRecapitulação - File Parser3Ponteiros e Memória
Fundamentos de Ponteiros em GoPonteiros para StructsPassagem por Valor vs ReferênciaA Função new()Garbage Collection em GoRecapitulação - Construtor de Lista Encadeada6Polimorfismo em Go
Polimorfismo via InterfacesDuck Typing em GoRegras de Satisfação de InterfaceColeções PolimórficasInjeção de DependênciaRecapitulação - Processador de Pagamentos9Concorrência e OOP
Fundamentos de GoroutinesCanais e ComunicaçãoCanais com Buffer vs. Sem BufferInstrução Selectsync.Mutex e sync.RWMutexsync.WaitGroupDesign de Structs Thread-SafeRecapitulação - Worker Pool