
🧐 ¿Qué es la Inyección de Dependencias?
La Inyección de Dependencias (DI) es un patrón de diseño en el que una clase recibe sus dependencias desde fuera en lugar de crearlas internamente. En vez de que un servicio instancie directamente las clases concretas que necesita, recibe una interfaz (abstracción) a través de su constructor.
Esta simple técnica tiene un gran impacto en como de testeable, flexible y mantenible resulta tu código.
🚫 El Problema: Acoplamiento Fuerte
Imagina una clase UserService que necesita obtener usuarios de una base de datos. Sin DI, esta clase crea directamente su propio repositorio:
class UserRepository {
findById(id: string): User {
// Accede a la base de datos real
return database.query(`SELECT * FROM users WHERE id = ${id}`);
}
}
class UserService {
private userRepo = new UserRepository(); // ❌ Dependencia hardcodeada
getUser(id: string): User {
return this.userRepo.findById(id);
}
}
¿Qué tiene de malo?
UserServiceestá fuertemente acoplado aUserRepository- No puedes testear
UserServicesin una conexión real a la base de datos - Si quieres cambiar la fuente de datos (por ejemplo, de SQL a una API), tienes que modificar
UserService
✅ La Solución: Depende de Interfaces, no de Implementaciones
La idea clave es:
- Definir una interfaz para la dependencia
- Hacer que el servicio dependa de la interfaz, no de la clase concreta
- Inyectar la implementación concreta a través del constructor
/** Paso 1: Definir la interfaz **/
interface IUserRepository {
findById(id: string): User;
}
/** Paso 2: El servicio depende de la interfaz **/
class UserService {
constructor(private userRepo: IUserRepository) {} // 💡 Parameter properties
getUser(id: string): User {
return this.userRepo.findById(id);
}
}
/** Paso 3: Crear la implementación real **/
class UserRepository implements IUserRepository {
findById(id: string): User {
return database.query(`SELECT * FROM users WHERE id = ${id}`);
}
}
Ahora, a UserService le da igual qué implementación recibe. Solo conoce IUserRepository.
/** Uso en producción **/
const userRepo = new UserRepository();
const userService = new UserService(userRepo);
🧪 Por Qué Importa: Testing
Aquí es donde la DI realmente brilla. Como UserService depende de una interfaz, podemos crear una implementación mock para los tests, sin necesidad de base de datos:
/** Implementación mock para tests **/
class UserRepositoryMock implements IUserRepository {
findById(id: string): User {
return { id, name: "Test User", email: "test@example.com" };
}
}
/** Test unitario (¡sin base de datos!) **/
const mockRepo = new UserRepositoryMock();
const userService = new UserService(mockRepo);
const user = userService.getUser("123");
assert(user.name === "Test User"); // ✅ Rápido, aislado, fiable
🚫 Sin DI: Tus tests necesitan una base de datos real, haciéndolos lentos y difíciles de configurar.
✅ Con DI: Los tests son rápidos, aislados y están completamente bajo tu control.
📊 Antes vs Después: Resumen Visual
Sin DI (con acoplamiento):
UserService ──────► UserRepository ──────► Database
(no se puede (concreto)
cambiar)
Con DI (sin acoplamiento):
UserService ──────► IUserRepository (interfaz)
│
┌────┴─────┐
▼ ▼
UserRepository UserRepositoryMock
(producción) (testing)
La interfaz actúa como un contrato: cualquier clase que la implemente se puede conectar a UserService. Esto hace que tu código sea flexible y tus tests fiables.
💡 Puntos Clave
- Depende de abstracciones (interfaces), no de implementaciones concretas
- Inyecta las dependencias a través del constructor en lugar de crearlas internamente
- Esto hace que tus clases sean fáciles de testear intercambiando implementaciones reales por mocks
- También hace que tu código sea más fácil de cambiar (cambia un repositorio SQL por un cliente API sin tocar el servicio)
Si conoces los principios SOLID, notarás que la Inyección de Dependencias es la aplicación práctica del Principio de Inversión de Dependencias (DIP): "Los módulos de alto nivel no deben depender de módulos de bajo nivel. Ambos deben depender de abstracciones."
Conclusión
La Inyección de Dependencias es uno de esos patrones que, una vez lo entiendes, querrás aplicar en todas partes. Mantiene tu código desacoplado, tus tests rápidos y fiables, y tu arquitectura flexible.
La próxima vez que escribas new SomeDependency() dentro de una clase, para y pregúntate: ¿debería inyectar esto? 🎯