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 integrado ya tiene. Esto se denomina ocultación y te permite reemplazar métodos promovidos con un comportamiento personalizado.
Cuando defines un método en la estructura externa con el mismo nombre que un método incorporado, 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()) // Woof! - método de Dog
fmt.Println(d.Animal.Speak()) // Algún sonido - método de Animal
}
El método integrado no ha desaparecido. Aún puedes acceder a él explícitamente mediante el nombre del tipo integrado. Este patrón resulta útil cuando quieres personalizar el comportamiento y mantener disponible la implementación original, quizá para llamarla desde dentro de tu método sobrescrito:
func (d Dog) Speak() string {
return d.Animal.Speak() + " (but actually Woof!)"
}
El sombreado te da control sobre qué comportamiento se expone, al tiempo que mantiene el acceso al original mediante una calificación explícita.
Desafío
FácilConstruyamos un sistema de notificaciones en el que diferentes canales de notificación puedan personalizar la forma en que formatean y entregan los mensajes. Usarás el sombreado de métodos para permitir que canales específicos sobrescriban el comportamiento predeterminado y, al mismo tiempo, puedan acceder a la implementación original cuando sea necesario.
Organizarás tu código en tres archivos:
base.go: Crea una estructuraBaseNotificationcon camposTitleyPriority(ambos de tipo string). Asígnale 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 incorporenBaseNotificationy oculten el métodoFormatcon sus propias implementaciones:EmailNotificationcon un campoRecipient. Su métodoFormat()debe devolverEmail to [Recipient]: [Priority] TitleSlackNotificationcon un campoChannel. Su métodoFormat()debe llamar al métodoBaseNotification.Format()incorporado y anteponer#[Channel]:al resultado
main.go: Lee los detalles de la notificación desde la entrada, crea ambos tipos de notificaciones y demuestra el sombreado imprimiendo:- La salida formateada de
EmailNotification(usando su método oculto) - El formato base de
EmailNotification(llamando explícitamente al método incorporado) - 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, dadas las entradas Server Alert, HIGH, admin@company.com y ops-alerts, la salida debería ser:
Email to admin@company.com: HIGH Server Alert
[HIGH] Server Alert
#ops-alerts: [HIGH] Server AlertObserva cómo EmailNotification reemplaza por completo el formato 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 eficazmente el sombreado de métodos.
Pruébalo tú mismo
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Lee el título de la notificación
scanner.Scan()
title := scanner.Text()
// Lee el nivel de prioridad
scanner.Scan()
priority := scanner.Text()
// Lee el destinatario del email
scanner.Scan()
recipient := scanner.Text()
// Lee el nombre del canal de Slack
scanner.Scan()
channel := scanner.Text()
// TODO: Crea un EmailNotification con la notificación base, title, priority y recipient
// TODO: Crea un SlackNotification con la notificación base, title, priority y channel
// TODO: Imprime la salida formateada de EmailNotification (usando su método sombreado)
// TODO: Imprime el formato base de EmailNotification (llamando explícitamente al método embebido)
// TODO: Imprime 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 PoolPractica por tu cuenta: Compilador de Go online