Implementación implícita
Parte de la sección Programación Orientada a Objetos del Journey de GO de Coddy. Lección 26 de 107.
En muchos lenguajes orientados a objetos como Java o C#, debes declarar explícitamente que un tipo implementa una interfaz usando palabras clave como implements. Go adopta un enfoque diferente: la implementación de interfaces es implícita.
Un tipo satisface una interfaz simplemente implementando todos sus métodos. No es necesario declarar la relación en ningún lugar. El compilador lo determina automáticamente.
type Writer interface {
Write(data string) int
}
type FileWriter struct {
Filename string
}
// FileWriter implementa implícitamente Writer
func (f FileWriter) Write(data string) int {
fmt.Println("Writing to", f.Filename)
return len(data)
}
Observa que no hay ninguna declaración implements Writer en FileWriter. Como FileWriter tiene un método Write(data string) int que coincide con la firma de la interfaz, satisface automáticamente la interfaz Writer.
Este diseño tiene una consecuencia poderosa: puedes definir interfaces después de que los tipos ya existan. Si una biblioteca de terceros tiene un tipo con métodos que necesitas, puedes crear una interfaz que ya cumpla sin modificar el código original.
func Save(w Writer, content string) {
w.Write(content)
}
func main() {
fw := FileWriter{Filename: "data.txt"}
Save(fw, "Hello!") // Funciona porque FileWriter satisface Writer
}
La implementación implícita mantiene el código de Go desacoplado y flexible. Los tipos no necesitan conocer las interfaces de antemano, lo que facilita componer sistemas a partir de partes independientes.
Desafío
FácilConstruyamos un sistema de entrega de mensajes que demuestre cómo los tipos satisfacen implícitamente las interfaces en Go. Crearás diferentes tipos de mensajeros que satisfacen todos la misma interfaz, sin declarar nunca esa relación explícitamente.
Organizarás tu código en dos archivos:
messengers.go: Define una interfazMessengercon un solo métodoSend(message string) string. Después, crea dos estructuras que satisfarán implícitamente esta interfaz:EmailMessengercon un campoAddress(cadena)SMSMessengercon un campoPhoneNumber(cadena)
Sendque devuelva una cadena que describa la entrega. Ninguna estructura debe declarar explícitamente que implementaMessenger: Go lo determinará automáticamente basándose en las firmas de los métodos.main.go: Crea una función llamadaDeliverMessageque acepte cualquierMessengery una cadena de mensaje, y después devuelva el resultado de llamar aSend. Lee la información de contacto de la entrada, crea ambos tipos de mensajeros y usa tu función para entregar mensajes a través de cada uno.
Se proporcionarán las siguientes entradas:
- Línea 1: Dirección de correo electrónico
- Línea 2: Número de teléfono
- Línea 3: Mensaje que se enviará
Tus métodos Send deben devolver cadenas con estos formatos:
- EmailMessenger:
Email to [Address]: [message] - SMSMessenger:
SMS to [PhoneNumber]: [message]
Por ejemplo, dadas alice@example.com, 555-1234 y Hello!, tu salida debería ser:
Email to alice@example.com: Hello!
SMS to 555-1234: Hello!La idea clave aquí es que tu función DeliverMessage acepta cualquier Messenger, y tanto EmailMessenger como SMSMessenger satisfacen esa interfaz simplemente por tener un método Send coincidente. No hay ninguna palabra clave implements: la implementación implícita de Go se encarga de todo.
Pruébalo tú mismo
package main
import (
"bufio"
"fmt"
"os"
)
// TODO: Crea una función DeliverMessage que acepte cualquier Messenger
// y una cadena de mensaje, luego devuelve el resultado de llamar a Send
func main() {
reader := bufio.NewReader(os.Stdin)
// Read email address
email, _ := reader.ReadString('\n')
email = email[:len(email)-1]
// Lee el número de teléfono
phone, _ := reader.ReadString('\n')
phone = phone[:len(phone)-1]
// Lee el mensaje
message, _ := reader.ReadString('\n')
if len(message) > 0 && message[len(message)-1] == '\n' {
message = message[:len(message)-1]
}
// TODO: Crea un EmailMessenger con la dirección de correo electrónico
// TODO: Crea un SMSMessenger con el número de teléfono
// TODO: Usa DeliverMessage para enviar el mensaje a través de cada messenger
// e imprime los resultados
}
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