Menu
Coddy logo textTech

Inyección de dependencias

Parte de la sección Programación Orientada a Objetos del Journey de GO de Coddy — lección 45 de 107.

La Inyección de Dependencias es una técnica en la que un struct recibe sus dependencias desde el exterior en lugar de crearlas internamente. En Go, las interfaces hacen que este patrón sea natural y potente.

En lugar de codificar de forma rígida una implementación específica dentro de una estructura, se acepta una interfaz. Esto permite intercambiar implementaciones sin cambiar el código de la estructura:

type Notifier interface {
    Send(message string) string
}

type EmailNotifier struct{}
func (e EmailNotifier) Send(message string) string {
    return "Email: " + message
}

type SMSNotifier struct{}
func (s SMSNotifier) Send(message string) string {
    return "SMS: " + message
}

Ahora crea una estructura que dependa de la interfaz, no de un tipo concreto:

type OrderService struct {
    notifier Notifier  // dependencia inyectada a través de la interfaz
}

func NewOrderService(n Notifier) *OrderService {
    return &OrderService{notifier: n}
}

func (o *OrderService) PlaceOrder(item string) string {
    return o.notifier.Send("Order placed: " + item)
}

El OrderService no sabe ni le importa si está usando correo electrónico o SMS. Inyectas la dependencia al crear el servicio:

func main() {
    emailService := NewOrderService(EmailNotifier{})
    fmt.Println(emailService.PlaceOrder("Book"))  // Email: Pedido realizado: Book
    
    smsService := NewOrderService(SMSNotifier{})
    fmt.Println(smsService.PlaceOrder("Phone"))   // SMS: Pedido realizado: Phone
}

Este enfoque hace que tu código sea más flexible y fácil de probar. Durante las pruebas, puedes inyectar un notificador simulado (mock) que no envíe mensajes realmente. En producción, inyectas la implementación real. El struct permanece sin cambios en ambos escenarios.

challenge icon

Desafío

Fácil

Vamos a construir un sistema de registro que demuestre el poder de la inyección de dependencias. Crearás un servicio que puede escribir registros en diferentes destinos, sin que el servicio sepa o le importe a dónde van realmente esos registros.

Organizarás tu código en tres archivos:

  • logger.go: Define una interfaz Logger que requiera un método Log(message string) string. Luego crea dos implementaciones de logger diferentes:
    • ConsoleLogger con un campo Prefix; su método Log devuelve [CONSOLE] [Prefix]: [message]
    • FileLogger con un campo Filename; su método Log devuelve [FILE:[Filename]] [message]
  • service.go: Crea un struct AppService que dependa de la interfaz Logger (no de un tipo concreto). Incluye una función constructora NewAppService que acepte un Logger y devuelva un puntero a AppService. Dale a AppService un método llamado DoWork(task string) string que utilice el logger inyectado para registrar el mensaje Processing: [task] y devuelva lo que sea que el logger devuelva.
  • main.go: Lee la configuración de la entrada, crea ambos tipos de logger, inyecta cada uno en instancias separadas de AppService y llama a DoWork en cada servicio con la tarea proporcionada. Imprime el resultado de cada llamada al servicio.

Se proporcionarán las siguientes entradas:

  • Línea 1: Prefijo del logger de consola
  • Línea 2: Nombre de archivo del logger de archivo
  • Línea 3: Tarea a procesar

Por ejemplo, dados INFO, app.log y user authentication, tu salida debería ser:

[CONSOLE] INFO: Processing: user authentication
[FILE:app.log] Processing: user authentication

Observa cómo AppService no sabe si está registrando en una consola o en un archivo; simplemente llama a Log en cualquier logger que se haya inyectado. Esta flexibilidad es la esencia de la inyección de dependencias: el mismo código de servicio funciona con implementaciones de registro completamente diferentes.

Hoja de referencia

La Inyección de Dependencias es una técnica en la que una estructura recibe sus dependencias desde el exterior en lugar de crearlas internamente. En Go, las interfaces hacen que este patrón sea natural y potente.

Define una interfaz para la dependencia:

type Notifier interface {
    Send(message string) string
}

Crea implementaciones concretas de la interfaz:

type EmailNotifier struct{}
func (e EmailNotifier) Send(message string) string {
    return "Email: " + message
}

type SMSNotifier struct{}
func (s SMSNotifier) Send(message string) string {
    return "SMS: " + message
}

Crea una estructura que dependa de la interfaz, no de un tipo concreto:

type OrderService struct {
    notifier Notifier  // dependencia inyectada a través de la interfaz
}

func NewOrderService(n Notifier) *OrderService {
    return &OrderService{notifier: n}
}

func (o *OrderService) PlaceOrder(item string) string {
    return o.notifier.Send("Order placed: " + item)
}

Inyecta la dependencia al crear el servicio:

emailService := NewOrderService(EmailNotifier{})
fmt.Println(emailService.PlaceOrder("Book"))  // Email: Order placed: Book

smsService := NewOrderService(SMSNotifier{})
fmt.Println(smsService.PlaceOrder("Phone"))   // SMS: Order placed: Phone

Este enfoque hace que el código sea más flexible y fácil de probar al permitir que se intercambien diferentes implementaciones sin cambiar el código de la estructura.

Pruébalo tú mismo

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	reader := bufio.NewReader(os.Stdin)
	
	// Leer el prefijo del registrador de consola
	prefix, _ := reader.ReadString('\n')
	prefix = prefix[:len(prefix)-1]
	
	// Leer el nombre del archivo del registrador de archivos
	filename, _ := reader.ReadString('\n')
	filename = filename[:len(filename)-1]
	
	// Leer la tarea a procesar
	task, _ := reader.ReadString('\n')
	if len(task) > 0 && task[len(task)-1] == '\n' {
		task = task[:len(task)-1]
	}
	
	// TODO: Crear un ConsoleLogger con el prefijo
	
	// TODO: Crear un FileLogger con el nombre de archivo
	
	// TODO: Crear un AppService con el ConsoleLogger inyectado
	
	// TODO: Crear otro AppService con el FileLogger inyectado
	
	// TODO: Llamar a DoWork en cada servicio con la tarea e imprimir los resultados
	fmt.Println("result1")
	fmt.Println("result2")
}
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