Menu
Coddy logo textTech

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.

challenge icon

Desafío

Fácil

Vamos 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 interfaz Notifier con un único método Notify(message string) string. Luego, crea tres structs diferentes que satisfagan este contrato:
    • ConsoleNotifier con un campo Prefix
    • FileNotifier con un campo Filename
    • WebhookNotifier con un campo URL
    Cada struct debe implementar el método Notify para devolver una cadena que describa cómo se enviaría la notificación.
  • main.go: Crea una función llamada BroadcastAlert que acepte un slice de valores Notifier y una cadena message. Esta función debe llamar a Notify en 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")
}
quiz iconPonte a prueba

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