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.
Considerar 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 y cuando cumpla este contrato».
Este enfoque basado en contratos te permite escribir funciones que se centran en el comportamiento en lugar de en tipos específicos. Considera una función que necesita procesar datos de diversas 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 una base de datos, un cliente de API o un lector de archivos. Solo requiere que la entrada pueda ejecutar FetchData(). Cualquier tipo que cumpla 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 contrato también hace que tu código sea más fácil de probar. Durante las pruebas, puedes crear un tipo simulado que cumpla la interfaz sin necesitar bases de datos reales ni conexiones de red. La función que se está probando no nota la diferencia porque el contrato se cumple.
Desafío
FácilConstruyamos un sistema de notificaciones que demuestre cómo las interfaces actúan como contratos. Crearás una interfaz Notifier que define qué significa enviar una notificación y, después, implementarás varios canales de notificación que respeten este contrato.
Organizarás tu código en dos archivos:
notifiers.go: Define una interfazNotifiercon un único métodoNotify(message string) string. Después, crea tres estructuras 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 de mensaje. 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 transmite una alerta a través de todos ellos.
Se proporcionarán las siguientes entradas:
- Línea 1: Prefijo de la consola
- Línea 2: Nombre del archivo
- Línea 3: URL del webhook
- Línea 4: Mensaje de alerta
Tus métodos Notify deben devolver cadenas con estos formatos:
- ConsoleNotifier:
[Prefix] [message] - FileNotifier:
Writing to [Filename]: [message] - WebhookNotifier:
POST [URL]: [message]
Por ejemplo, dadas las entradas 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!La ventaja de este diseño es que tu función BroadcastAlert no conoce ni necesita conocer los tipos específicos de notificadores. Solo requiere que cada uno cumpla el contrato Notifier. Más adelante podrías añadir un SlackNotifier o un EmailNotifier sin cambiar la función de transmisión.
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 una cadena message
// - Llamar a Notify en cada notifier e imprimir los resultados
func BroadcastAlert(notifiers []Notifier, message string) {
// TODO: Recorrer los notifiers e imprimir el resultado de cada notificación
}
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Leer el prefijo de consola
scanner.Scan()
prefix := scanner.Text()
// Leer el nombre de 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: Crear instancias de los tres tipos de notifier
// - ConsoleNotifier con el prefijo
// - 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 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 PoolPractica por tu cuenta: Compilador de Go online