Duck Typing en Go
Parte de la sección Programación Orientada a Objetos del Journey de GO de Coddy — lección 42 de 107.
El término duck typing proviene del refrán: "Si camina como un pato y grazna como un pato, entonces es un pato". En Go, esto significa que un tipo no necesita declarar explícitamente que implementa una interfaz; solo necesita tener los métodos adecuados.
Considera esta interfaz y dos tipos completamente no relacionados:
type Quacker interface {
Quack() string
}
type Duck struct{}
func (d Duck) Quack() string { return "Quack!" }
type Robot struct{}
func (r Robot) Quack() string { return "Beep-quack!" }
Ni Duck ni Robot mencionan a Quacker en ninguna parte de sus definiciones. Sin embargo, ambos satisfacen la interfaz simplemente porque tienen un método Quack() con la firma correcta:
func MakeItQuack(q Quacker) {
fmt.Println(q.Quack())
}
func main() {
MakeItQuack(Duck{}) // Quack!
MakeItQuack(Robot{}) // Beep-quack!
}
Esta satisfacción implícita es poderosa porque te permite definir interfaces después de que los tipos ya existan. Puedes crear una interfaz que coincida con tipos de paquetes externos sin modificar su código fuente. Los tipos no necesitan conocer tu interfaz; simplemente necesitan comportarse correctamente.
El duck typing en Go te ofrece la flexibilidad de los lenguajes dinámicos mientras mantiene la seguridad de tipos en tiempo de compilación. El compilador verifica que los tipos realmente tengan los métodos requeridos antes de permitir que se utilicen como valores de interfaz.
Desafío
FácilVamos a construir un sistema de mensajería que demuestre el duck typing en acción. Crearás tipos completamente no relacionados que pueden enviar mensajes a través de una interfaz común, sin que ninguno de ellos declare explícitamente que la implementa.
Organizarás tu código en tres archivos:
messengers.go: Crea tres structs no relacionados que casualmente comparten el mismo comportamiento:Phonecon un campoNumber—su métodoSendMessage(text string) stringdevuelveSMS to [Number]: [text]Computercon un campoEmail—su métodoSendMessage(text string) stringdevuelveEmail to [Email]: [text]Pigeoncon un campoName—su métodoSendMessage(text string) stringdevuelve[Name] carries: [text]
sender.go: Define una interfazMessengerque requiera un métodoSendMessage(text string) string. Crea una función llamadaBroadcastque reciba un slice deMessengery un string de mensaje, y luego devuelva un slice de strings que contenga el resultado de llamar aSendMessageen cada mensajero.main.go: Lee los detalles de la entrada, crea uno de cada tipo de mensajero, coléctalos en un slice deMessengery usaBroadcastpara enviar un mensaje a través de todos ellos. Imprime cada resultado en su propia línea.
Se proporcionarán las siguientes entradas:
- Línea 1: Número de teléfono
- Línea 2: Dirección de correo electrónico
- Línea 3: Nombre de la paloma
- Línea 4: Mensaje a difundir
Por ejemplo, dados 555-1234, alice@mail.com, Winston y Hello World, tu salida debería ser:
SMS to 555-1234: Hello World
Email to alice@mail.com: Hello World
Winston carries: Hello WorldLa idea clave aquí es que Phone, Computer y Pigeon no tienen nada en común: no comparten un tipo base ni implementan explícitamente Messenger. Sin embargo, debido a que todos "graznan" de la misma manera (tienen el método coincidente), Go permite que todos se utilicen como valores de Messenger. Esto es el duck typing en funcionamiento.
Hoja de referencia
En Go, el duck typing significa que un tipo no necesita declarar explícitamente que implementa una interfaz; solo necesita tener los métodos requeridos con firmas coincidentes.
Un tipo satisface implícitamente una interfaz si tiene todos los métodos que la interfaz requiere:
type Quacker interface {
Quack() string
}
type Duck struct{}
func (d Duck) Quack() string { return "Quack!" }
type Robot struct{}
func (r Robot) Quack() string { return "Beep-quack!" }
Tanto Duck como Robot satisfacen la interfaz Quacker sin mencionarla en sus definiciones:
func MakeItQuack(q Quacker) {
fmt.Println(q.Quack())
}
func main() {
MakeItQuack(Duck{}) // Quack!
MakeItQuack(Robot{}) // Beep-quack!
}
Esta satisfacción implícita permite definir interfaces después de que los tipos ya existan, e incluso crear interfaces que coincidan con tipos de paquetes externos sin modificar su código fuente. El compilador verifica en tiempo de compilación que los tipos tengan los métodos requeridos, proporcionando seguridad de tipos mientras se mantiene la flexibilidad.
Pruébalo tú mismo
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Leer el número de teléfono
scanner.Scan()
phoneNumber := scanner.Text()
// Leer la dirección de correo electrónico
scanner.Scan()
email := scanner.Text()
// Leer el nombre de la paloma
scanner.Scan()
pigeonName := scanner.Text()
// Leer el mensaje a difundir
scanner.Scan()
message := scanner.Text()
// TODO: Crear uno de cada tipo de mensajero (Phone, Computer, Pigeon)
// TODO: Recolectar todos los mensajeros en un slice de Messenger
// TODO: Usar Broadcast para enviar el mensaje a través de todos los mensajeros
// TODO: Imprimir cada resultado en su propia línea
// Marcador de posición para usar variables (eliminar al implementar)
_ = phoneNumber
_ = email
_ = pigeonName
_ = message
}
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