Dependency Injection en TypeScript: para principiantes
\n\nEn el mundo del desarrollo de software, a menudo nos encontramos con el término Dependency Injection (DI), o inyección de dependencias. Para un desarrollador principiante en TypeScript, esto puede sonar intimidante, pero en realidad es un patrón simple y poderoso que hace que tu código sea más limpio, más fácil de probar y más fácil de mantener. En este artículo, analizaremos qué es DI, por qué es necesario y cómo aplicarlo en TypeScript en la práctica.
\n\n¿Qué es Dependency Injection?
\n\nImagina que estás armando una computadora. En lugar de integrar el procesador directamente en la placa base en la fábrica, dejas una ranura en la que puedes insertar cualquier procesador compatible. Dependency Injection funciona bajo el mismo principio: en lugar de que la clase cree sus propias dependencias (por ejemplo, una base de datos o un cliente HTTP), las recibe desde el exterior.
\n\nEn palabras simples, DI es cuando un objeto no crea sus "ayudantes" internamente, sino que los recibe ya listos a través del constructor o un método.
\n\nEl problema sin DI: Alto acoplamiento
\n\nConsideremos un ejemplo que se encuentra a menudo en proyectos reales:
\n\nclass UserService {\n private database: MySQLDatabase;\n\n constructor() {\n this.database = new MySQLDatabase(); // ¡Alto acoplamiento!\n }\n\n getUser(id: number) {\n return this.database.findUserById(id);\n }\n}\n\nAquí, UserService está fuertemente acoplado a MySQLDatabase. Si deseas migrar a PostgreSQL o escribir pruebas unitarias con una base de datos falsa, tendrías que reescribir toda la clase. Esto viola el principio Open-Closed de SOLID.
Solución con DI: Flexibilidad y capacidad de prueba
\n\nAhora reescribamos la misma clase usando DI:
\n\ninterface Database {\n findUserById(id: number): User | null;\n}\n\nclass UserService {\n constructor(private database: Database) {} // La dependencia se pasa desde el exterior\n\n getUser(id: number) {\n return this.database.findUserById(id);\n }\n}\n\n// Uso\nconst mySQL = new MySQLDatabase();\nconst service = new UserService(mySQL); // Inyectamos la dependencia\n\nAhora UserService no sabe con qué base de datos está trabajando. Puedes reemplazar fácilmente MySQLDatabase por PostgreSQLDatabase o MockDatabase para las pruebas.
Formas principales de inyectar dependencias
\n\nEn TypeScript, existen tres formas principales de inyectar dependencias. Veamos cada una de ellas.
\n\n1. Inyección por constructor (Constructor Injection)
\n\nEsta es la forma más común y recomendada. Las dependencias se pasan a través del constructor de la clase.
\n\nclass Logger {\n log(message: string) {\n console.log(`[LOG]: ${message}`);\n }\n}\n\nclass OrderService {\n constructor(private logger: Logger) {}\n\n createOrder(data: any) {\n this.logger.log('Order created');\n // lógica de creación de pedido\n }\n}\n\nconst logger = new Logger();\nconst orderService = new OrderService(logger);\n\n2. Inyección por propiedad (Property Injection)
\n\nLa dependencia se establece directamente en una propiedad del objeto después de su creación. Se usa con menos frecuencia, ya que puede provocar una inicialización incompleta del objeto.
\n\nclass EmailService {\n sendEmail(to: string, message: string) {\n console.log(`Email sent to ${to}: ${message}`);\n }\n}\n\nclass NotificationService {\n emailService!: EmailService; // Afirmación de tipo\n\n notifyUser(user: string) {\n this.emailService.sendEmail(user, 'Hello!');\n }\n}\n\nconst notification = new NotificationService();\nnotification.emailService = new EmailService(); // Establecemos la propiedad\nnotification.notifyUser('user@example.com');\n\n