La interfaz como contrato
Parte de la sección Programación Orientada a Objetos del Journey de GO de Coddy — lección 27 de 107.
Pensar en las interfaces como contratos te ayuda a diseñar mejores programas en Go. Cuando una función acepta un tipo de interfaz, está diciendo: "No me importa qué tipo concreto me des, siempre que cumpla con este contrato".
Este pensamiento basado en contratos le permite escribir funciones que se centran en el comportamiento en lugar de en tipos específicos. Considere una función que necesita procesar datos de varias fuentes:
type DataSource interface {
FetchData() string
}
func ProcessData(src DataSource) {
data := src.FetchData()
fmt.Println("Processing:", data)
}La función ProcessData no sabe ni le importa si recibe una conexión a la base de datos, un cliente de API o un lector de archivos. Solo requiere que la entrada pueda FetchData(). Cualquier tipo que cumpla con este contrato funciona.
type Database struct{ ConnectionString string }
func (d Database) FetchData() string { return "data from DB" }
type APIClient struct{ Endpoint string }
func (a APIClient) FetchData() string { return "data from API" }
func main() {
db := Database{ConnectionString: "localhost"}
api := APIClient{Endpoint: "https://api.example.com"}
ProcessData(db) // ¡Funciona!
ProcessData(api) // ¡También funciona!
}Este enfoque de contratos hace que tu código sea más fácil de probar también. Durante las pruebas, puedes crear un tipo simulado (mock) que satisfaga la interfaz sin necesidad de bases de datos reales o conexiones de red. La función bajo prueba no nota la diferencia porque el contrato se cumple.
Desafío
FácilVamos a construir un sistema de notificación que demuestre cómo las interfaces actúan como contratos. Crearás una interfaz Notifier que define lo que significa enviar una notificación, y luego implementarás múltiples canales de notificación que cumplan con este contrato.
Organizarás tu código en dos archivos:
notifiers.go: Define una interfazNotifiercon un único métodoNotify(message string) string. Luego, crea tres structs diferentes que satisfagan este contrato:ConsoleNotifiercon un campoPrefixFileNotifiercon un campoFilenameWebhookNotifiercon un campoURL
Notifypara devolver una cadena que describa cómo se enviaría la notificación.main.go: Crea una función llamadaBroadcastAlertque acepte un slice de valoresNotifiery una cadena message. Esta función debe llamar aNotifyen cada notificador e imprimir los resultados. Lee la configuración de la entrada, crea los tres tipos de notificadores y emite una alerta a través de todos ellos.
Se proporcionarán las siguientes entradas:
- Línea 1: Prefijo de consola
- Línea 2: Nombre de archivo
- Línea 3: URL del webhook
- Línea 4: Mensaje de alerta
Tus métodos Notify deben devolver cadenas en estos formatos:
- ConsoleNotifier:
[Prefix] [message] - FileNotifier:
Writing to [Filename]: [message] - WebhookNotifier:
POST [URL]: [message]
Por ejemplo, dados ALERT:, log.txt, https://hooks.example.com, y Server down!, tu salida debería ser:
ALERT: Server down!
Writing to log.txt: Server down!
POST https://hooks.example.com: Server down!El poder de este diseño es que tu función BroadcastAlert no conoce ni le importan los tipos de notificadores específicos; solo requiere que cada uno cumpla con el contrato Notifier. Podrías añadir un SlackNotifier o EmailNotifier más tarde sin cambiar la función de difusión en absoluto.
Hoja de referencia
Las interfaces en Go actúan como contratos que definen el comportamiento requerido sin especificar tipos concretos. Cuando una función acepta un tipo de interfaz, se puede utilizar cualquier tipo que implemente los métodos de la interfaz.
Define una interfaz especificando las firmas de los métodos:
type DataSource interface {
FetchData() string
}Las funciones que aceptan interfaces funcionan con cualquier tipo que cumpla el contrato:
func ProcessData(src DataSource) {
data := src.FetchData()
fmt.Println("Processing:", data)
}Múltiples tipos pueden implementar la misma interfaz:
type Database struct{ ConnectionString string }
func (d Database) FetchData() string { return "data from DB" }
type APIClient struct{ Endpoint string }
func (a APIClient) FetchData() string { return "data from API" }
func main() {
db := Database{ConnectionString: "localhost"}
api := APIClient{Endpoint: "https://api.example.com"}
ProcessData(db) // ¡Funciona!
ProcessData(api) // ¡También funciona!
}Este enfoque basado en contratos permite:
- Funciones que se centran en el comportamiento en lugar de en tipos específicos
- Mejor capacidad de prueba a través de implementaciones simuladas (mock)
- Extensibilidad sin modificar el código existente
Pruébalo tú mismo
package main
import (
"bufio"
"fmt"
"os"
)
// BroadcastAlert envía un mensaje a través de todos los notifiers
// TODO: Implementa esta función para:
// - Aceptar un slice de valores Notifier y un string de mensaje
// - Llamar a Notify en cada notifier e imprimir los resultados
func BroadcastAlert(notifiers []Notifier, message string) {
// TODO: Recorre los notifiers e imprime cada resultado de notificación
}
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Leer el prefijo de la consola
scanner.Scan()
prefix := scanner.Text()
// Leer el nombre del archivo
scanner.Scan()
filename := scanner.Text()
// Leer la URL del webhook
scanner.Scan()
url := scanner.Text()
// Leer el mensaje de alerta
scanner.Scan()
message := scanner.Text()
// TODO: Crea instancias de los tres tipos de notifier
// - ConsoleNotifier con el prefix
// - FileNotifier con el filename
// - WebhookNotifier con la url
// TODO: Crea un slice de Notifier que contenga los tres notifiers
// TODO: Llama a BroadcastAlert con los notifiers y el message
_ = prefix
_ = filename
_ = url
_ = message
fmt.Println("TODO: Implement the solution")
}
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