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 utilizando 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. Debido a que 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 los métodos que necesitas, puedes crear una interfaz que este ya satisfaga 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 a 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 la composición de sistemas a partir de partes independientes.
Desafío
FácilVamos a construir 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 la misma interfaz, sin declarar nunca esa relación de forma explícita.
Organizarás tu código en dos archivos:
messengers.go: Define una interfazMessengercon un único métodoSend(message string) string. Luego, crea dos structs que satisfarán implícitamente esta interfaz:EmailMessengercon un campoAddress(string)SMSMessengercon un campoPhoneNumber(string)
Sendque devuelva un string describiendo la entrega. Ninguno de los structs 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 un string de mensaje, y luego devuelva el resultado de llamar aSend. Lee la información de contacto de la entrada, crea ambos tipos de mensajeros y utiliza 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 a enviar
Tus métodos Send deben devolver strings en estos formatos:
- EmailMessenger:
Email to [Address]: [message] - SMSMessenger:
SMS to [PhoneNumber]: [message]
Por ejemplo, dados 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 en ninguna parte; la implementación implícita de Go se encarga de todo.
Hoja de referencia
En Go, la implementación de interfaces es implícita. Un tipo satisface una interfaz simplemente implementando todos sus métodos; no se necesita ninguna declaración explícita.
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)
}
No hay una declaración implements Writer. Debido a que FileWriter tiene un método Write(data string) int que coincide con la firma de la interfaz, satisface automáticamente la interfaz Writer.
Puedes usar cualquier tipo que satisfaga una interfaz sin que el tipo conozca la interfaz:
func Save(w Writer, content string) {
w.Write(content)
}
func main() {
fw := FileWriter{Filename: "data.txt"}
Save(fw, "Hello!") // Funciona porque FileWriter satisface Writer
}
Este diseño permite definir interfaces después de que los tipos ya existan, haciendo que el código sea desacoplado y flexible.
Pruébalo tú mismo
package main
import (
"bufio"
"fmt"
"os"
)
// TODO: Crear una función DeliverMessage que acepte cualquier Messenger
// y una cadena de mensaje, luego devuelva el resultado de llamar a Send
func main() {
reader := bufio.NewReader(os.Stdin)
// Leer dirección de correo electrónico
email, _ := reader.ReadString('\n')
email = email[:len(email)-1]
// Leer número de teléfono
phone, _ := reader.ReadString('\n')
phone = phone[:len(phone)-1]
// Leer mensaje
message, _ := reader.ReadString('\n')
if len(message) > 0 && message[len(message)-1] == '\n' {
message = message[:len(message)-1]
}
// TODO: Crear un EmailMessenger con la dirección de correo electrónico
// TODO: Crear un SMSMessenger con el número de teléfono
// TODO: Usar DeliverMessage para enviar el mensaje a través de cada mensajero
// y mostrar 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 Pool