Introducción: El problema del acoplamiento rígido en Java
\n\nImagina que estás escribiendo una aplicación para enviar notificaciones. En tu clase NotificationService se crea directamente un objeto EmailSender. Todo funciona perfectamente, hasta que un día el cliente te pide agregar soporte para notificaciones SMS y Push. Tendrás que reescribir NotificationService, cambiar los constructores, agregar nuevas condiciones. El código se vuelve frágil, difícil de probar y de ampliar.
Este es el problema clásico del acoplamiento rígido (tight coupling). La solución se llama Dependency Injection (Inyección de Dependencias) — uno de los principios clave de diseño en Java y la base de frameworks como Spring. En este artículo, analizaremos qué es la Inyección de Dependencias en palabras sencillas, cómo implementarla y por qué ningún desarrollador Java profesional puede prescindir de ella.
\n\n¿Qué es la Inyección de Dependencias en palabras sencillas?
\n\nDependency Injection (DI) es un patrón de diseño en el cual un objeto recibe sus dependencias desde el exterior, en lugar de crearlas él mismo. En lugar de escribir new EmailSender() dentro de la clase, «inyectas» un objeto ya preparado a través del constructor, un setter o una interfaz.
DI está estrechamente relacionado con el principio de Inversion of Control (IoC) — inversión de control. Si antes tu clase gestionaba la creación de dependencias, ahora el control se transfiere a un contenedor externo (por ejemplo, Spring IoC).
\n\nPrincipales ventajas de DI:
\n- \n
- Bajo acoplamiento (loose coupling) — las clases dependen de abstracciones, no de implementaciones concretas. \n
- Simplificación de las pruebas — las dependencias se reemplazan fácilmente con mocks o stubs. \n
- Reutilización de código — un mismo servicio se puede usar en diferentes contextos. \n
- Facilidad de mantenimiento — cambiar la implementación no requiere modificar las clases consumidoras. \n
Tipos de inyección de dependencias en Java
\n\nExisten tres formas principales de implementar la Inyección de Dependencias en Java. Veamos cada una con un ejemplo.
\n\n1. Inyección por constructor (Constructor Injection)
\n\nLa forma más común y recomendada. Las dependencias se pasan a través de los parámetros del constructor y se guardan como campos de la clase (normalmente final).
// Interfaz para enviar mensajes\npublic interface MessageSender {\n void send(String message, String recipient);\n}\n\n// Implementación concreta — Email\npublic class EmailSender implements MessageSender {\n @Override\n public void send(String message, String recipient) {\n System.out.println("Enviando Email: " + message + " a " + recipient);\n }\n}\n\n// Servicio que usa DI a través del constructor\npublic class NotificationService {\n private final MessageSender sender;\n\n // La dependencia se inyecta a través del constructor\n public NotificationService(MessageSender sender) {\n this.sender = sender;\n }\n\n public void notifyUser(String message, String userEmail) {\n sender.send(message, userEmail);\n }\n}\n\n// Uso\npublic class Main {\n public static void main(String[] args) {\n MessageSender emailSender = new EmailSender();\n NotificationService service = new NotificationService(emailSender);\n service.notifyUser("¡Bienvenido!", "user@example.com");\n }\n}\n\nVentajas: el objeto se crea en un estado completamente listo, las dependencias son inmutables (immutable), fácil de probar.
\n\n2. Inyección por setter (Setter Injection)
\n\nLas dependencias se pasan a través de métodos setter después de crear el objeto. Se usa más a menudo para dependencias opcionales.
\n\npublic class NotificationService {\n private MessageSender sender;\n\n // Setter para inyectar la dependencia\n public void setSende