Shadowing de métodos incrustados
Parte de la sección Programación Orientada a Objetos del Journey de GO de Coddy — lección 39 de 107.
A veces necesitas que la estructura externa proporcione su propia implementación de un método que el tipo embebido ya tiene. Esto se llama shadowing, y te permite sobrescribir métodos promovidos con un comportamiento personalizado.
Cuando defines un método en la estructura externa con el mismo nombre que un método incrustado, el método externo tiene prioridad:
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!"
}
Ahora, cuando llamas a Speak() en un Dog, se utiliza el método externo:
func main() {
d := Dog{Animal: Animal{Name: "Rex"}, Breed: "Labrador"}
fmt.Println(d.Speak()) // ¡Guau! - método de Dog
fmt.Println(d.Animal.Speak()) // Algún sonido - método de Animal
}
El método incrustado no ha desaparecido. Todavía puedes acceder a él explícitamente a través del nombre del tipo incrustado. Este patrón es útil cuando quieres personalizar el comportamiento manteniendo disponible la implementación original, tal vez para llamarla desde dentro de tu método de anulación:
func (d Dog) Speak() string {
return d.Animal.Speak() + " (but actually Woof!)"
}
El sombreado le otorga control sobre qué comportamiento se expone mientras mantiene el acceso al original a través de una calificación explícita.
Desafío
FácilVamos a construir un sistema de notificaciones donde diferentes canales de notificación puedan personalizar cómo formatean y entregan los mensajes. Utilizarás el sombreado de métodos (method shadowing) para permitir que canales específicos anulen el comportamiento predeterminado, manteniendo la capacidad de acceder a la implementación original cuando sea necesario.
Organizarás tu código en tres archivos:
base.go: Crea una estructuraBaseNotificationcon los camposTitleyPriority(ambos strings). Dale un métodoFormat() stringque devuelva la notificación en un formato estándar:[Priority] Title.channels.go: Crea dos tipos de canales de notificación que incrustenBaseNotificationy sombreen el métodoFormatcon sus propias implementaciones:EmailNotificationcon un campoRecipient—suFormat()debe devolverEmail to [Recipient]: [Priority] TitleSlackNotificationcon un campoChannel—suFormat()debe llamar al métodoBaseNotification.Format()incrustado y anteponer#[Channel]:al resultado
main.go: Lee los detalles de la notificación de la entrada, crea ambos tipos de notificaciones y demuestra el sombreado imprimiendo:- La salida formateada de
EmailNotification(usando su método sombreado) - El formato base de
EmailNotification(llamando explícitamente al método incrustado) - La salida formateada de
SlackNotification(que utiliza internamente el método base)
- La salida formateada de
Se proporcionarán las siguientes entradas:
- Línea 1: Título de la notificación
- Línea 2: Nivel de prioridad
- Línea 3: Destinatario del correo electrónico
- Línea 4: Nombre del canal de Slack
Por ejemplo, dados Server Alert, HIGH, admin@company.com, y ops-alerts, tu salida debería ser:
Email to admin@company.com: HIGH Server Alert
[HIGH] Server Alert
#ops-alerts: [HIGH] Server AlertObserva cómo EmailNotification reemplaza completamente el formateo base, mientras que SlackNotification lo mejora llamando al método original y añadiendo el contexto del canal. Ambos enfoques demuestran diferentes formas de utilizar el sombreado de métodos de manera efectiva.
Hoja de referencia
Cuando defines un método en una estructura externa con el mismo nombre que un método de un tipo incrustado, el método externo oculta (anula) al método incrustado:
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!"
}
Al llamar al método en la estructura externa, el método externo tiene prioridad:
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
El método incrustado sigue siendo accesible mediante la calificación explícita utilizando el nombre del tipo incrustado. Puedes llamar a la implementación original desde dentro del método que lo anula:
func (d Dog) Speak() string {
return d.Animal.Speak() + " (but actually Woof!)"
}
Pruébalo tú mismo
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Leer el título de la notificación
scanner.Scan()
title := scanner.Text()
// Leer el nivel de prioridad
scanner.Scan()
priority := scanner.Text()
// Leer el destinatario del correo electrónico
scanner.Scan()
recipient := scanner.Text()
// Leer el nombre del canal de Slack
scanner.Scan()
channel := scanner.Text()
// TODO: Crear una EmailNotification con la notificación base, title, priority y recipient
// TODO: Crear una SlackNotification con la notificación base, title, priority y channel
// TODO: Imprimir la salida formateada de EmailNotification (usando su método sombreado)
// TODO: Imprimir el formato base de EmailNotification (llamando explícitamente al método embebido)
// TODO: Imprimir la salida formateada de SlackNotification
fmt.Println() // Marcador de posición - eliminar al implementar
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de POO en Go
Archivos externosEspacio de trabajo y módulos de GoPaquetes e importacionesNombres exportados vs. no exportadosIntroducción a la POO en GoStructs como clasesDefinición de métodos en structsReceptores de puntero vs. de valorInicialización de structsFunciones constructorasResumen - Calculadora simple4Interfaces
Introducción a las interfacesImplementación implícitaLa interfaz como contratoInterfaz vacía (any)Type AssertionType SwitchComposición de interfacesInterfaces Stringer y ErrorResumen: Calculadora de figuras7Encapsulamiento
Campos exportados vs. no exportadosEncapsulamiento a nivel de paqueteMétodos Getter y SetterOcultamiento de información en GoResumen - Registros de estudiantes10Genéricos (Go 1.18+)
Introducción a los GenéricosParámetros de tipoRestricciones de tipoStructs genéricosSolución alternativa para métodos genéricosResumen - Colección genérica13Patrones de diseño - Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón Abstract FactoryPatrón ObserverPatrón Strategy2Inmersión profunda en Tipos y Structs
Tipos básicos y compuestosDefiniciones de tipos personalizadosEtiquetas de StructsStructs anónimosStructs anidadosValores cero y por defectoRepaso - Libreta de contactos5Composición sobre herencia
Por qué Go no tiene herenciaConceptos básicos de incrustación de structsPromoción de métodosIncrustación de múltiples structsIncrustación vs AgregaciónShadowing de métodos incrustadosResumen - Jerarquía de empleados8Manejo de Errores y OOP
La interfaz errorTipos de errores personalizadosEnvoltura de errores (fmt.Errorf)Errores centinelaerrors.Is() y errors.As()Panic, Defer y RecoverResumen - Analizador de archivos3Punteros y Memoria
Fundamentos de punteros en GoPunteros a StructsPaso por valor vs referenciaLa función new()Garbage Collection en GoResumen - Constructor de Linked List6Polimorfismo en Go
Polimorfismo mediante interfacesDuck Typing en GoReglas de satisfacción de interfacesColecciones polimórficasInyección de dependenciasResumen - Procesador de pagos9Concurrencia y POO
Fundamentos de GoroutinesChannels y comunicaciónChannels con buffer vs. sin bufferSentencia Selectsync.Mutex y sync.RWMutexsync.WaitGroupDiseño de estructuras Thread-SafeResumen - Worker Pool