5 Inversión de control, refactoring, inyección de dependencias
Inversión de Control: Conceptos Fundamentales
Introducción a la Inversión de Control
- Se presenta el concepto de inversión de control, que se divide en dos enfoques: control directo e inverso.
- El control directo implica que el programador interactúa directamente con los objetos, pidiendo acciones específicas como obtener precios o cambiar valores.
- En contraste, el control inverso permite que un tercero llame a los métodos del objeto sin que este último sepa cuándo sucederá.
Comparación entre Control Directo e Inverso
- La diferencia clave radica en quién inicia la interacción: el programador (control directo) o un tercero (control inverso).
- Ejemplo práctico: al pedir un precio, en control directo se llama explícitamente al método; en control inverso, es el sistema quien solicita esa información.
Ejemplos Prácticos
- Se menciona una prenda que puede recibir un medio de pago para autorizar transacciones.
- En el enfoque de control inverso, la clase prenda debe implementar un contrato para devolver su precio cuando sea solicitada por otro objeto.
Entendiendo las Responsabilidades y Acoplamientos
Diferencias en Acoplamiento
- En el diseño con control directo, hay acoplamiento fuerte entre la prenda y el medio de pago; esto significa que uno depende del otro para funcionar correctamente.
- Con control inverso, la prenda queda disponible para ser utilizada por otros objetos sin necesidad de conocerlos directamente.
Impacto en Diseño y Flexibilidad
- Este cambio reduce la responsabilidad del objeto original al no tener que recordar llamar a otros métodos; ahora es el medio de pago quien solicita información.
Relaciones entre Objetos y Conocimiento Mutuo
¿Quién Conoce a Quién?
- Se discute cómo las relaciones entre clases afectan quién envía mensajes a quién. Por ejemplo, una prenda envía mensajes al medio de pago.
Cambios en Interfaces
- Al invertir el control, cambia también qué clase implementa qué interfaz. En algunos casos, es la prenda quien debe implementar una interfaz pagable.
Proceso de Autorización y Cambio Semántico
Cambios en Métodos Clave
- La inversión del control no solo afecta quién llama a quién sino también los métodos involucrados; por ejemplo, "pagar" podría convertirse en "autorizar".
Importancia del Contexto
- La percepción sobre si algo es un caso de control directo o inverso depende desde dónde se analice la situación.
Aplicaciones Prácticas: Framework JUnit como Ejemplo
Ejemplo Realista con JUnit
- Se utiliza JUnit como ejemplo práctico donde los desarrolladores crean clases pero nunca las llaman directamente; lo hace un framework externo.
Contratos y Llamadas Automáticas
- Los desarrolladores deben cumplir ciertos contratos (como anotaciones), confiando en que JUnit llamará sus métodos según sea necesario.
Comprendiendo Dependencias e Inyección
Definición de Dependencias
- Las dependencias son elementos necesarios para que un objeto funcione correctamente. Pueden ser parámetros o atributos requeridos por ese objeto.
Problemas Potenciales con Hardcoding
- Instanciar dependencias directamente puede llevar a problemas como falta de flexibilidad o dificultad para realizar pruebas unitarias efectivas.
- Si se necesita cambiar una implementación específica (por ejemplo, generador), será necesario modificar múltiples partes del código.
Comportamiento y Generadores de Prendas
Discusión sobre la responsabilidad del generador
- Se plantea la idea de que el generador tiene una responsabilidad en la creación de sugerencias, pero no en la selección de prendas, que debería ser del usuario.
- Se discute cómo las prendas deben ser pasadas como parámetros al generador para su correcta mezcla y selección.
Parametrización y sus ventajas
- La parametrización se presenta como una alternativa a tener instancias fijas, permitiendo mayor flexibilidad en el uso del generador.
- Se menciona que al recibir parámetros, se puede evaluar si el generador es una dependencia transitoria o fija.
Testeabilidad y complejidad
- La parametrización permite realizar pruebas más complejas al poder cambiar fácilmente los generadores utilizados durante las pruebas.
- Se concluye que esta solución es más testeable comparada con versiones anteriores donde había referencias estáticas.
Singleton vs. Service Locator
Problemas con el Singleton
- Se advierte que usar un singleton sin precauciones puede llevar a problemas similares a los de una referencia estática.
- El singleton limita la inyección de dependencias, lo cual afecta negativamente la flexibilidad del sistema.
Introducción al Service Locator
- El Service Locator se presenta como una forma de inyección de dependencias, permitiendo obtener instancias sin acoplarse directamente a ellas.
- Se explica que tanto la inyección de dependencias como el Service Locator son soluciones para gestionar dependencias en sistemas complejos.
Inversión de Control (IoC)
Concepto y aplicación
- La inversión de control implica delegar la responsabilidad de obtener dependencias a otro componente, facilitando así su gestión.
- En este contexto, se destaca cómo IoC permite desacoplar componentes dentro del sistema, mejorando su mantenibilidad.
Comparación entre enfoques
- Se discute cómo ambos enfoques (Service Locator e inyección directa) presentan diferentes tipos y grados de acoplamiento entre componentes.
- La diferencia radica en cómo los componentes interactúan con sus dependencias: ya sea mediante instanciación directa o a través del Service Locator.
Opciones para realizar cambios en el diseño
Parche rápido vs. Refactorización
- Se presentan dos opciones al hacer un cambio: un parche rápido que cumple con los requerimientos o una refactorización más compleja.
- La refactorización se considera cuando el cambio no es trivial, como cuando el diseño original no cumple con los requisitos establecidos.
Proceso de Refactorización
- La refactorización implica adaptar el diseño a nuevos requerimientos, lo cual puede ser más complejo que simplemente aplicar un parche.
- Generalmente, la refactorización no agrega funcionalidad nueva; su objetivo es preparar el diseño para soportar futuros cambios.
Flexibilidad y Simplicidad
- El proceso de refactorización puede hacer que el código sea más flexible o más simple, dependiendo de las necesidades del proyecto.
- Adaptar el diseño facilita la incorporación de nuevas funcionalidades en el futuro.
Importancia de separar procesos
Mantener claridad en la implementación
- Es crucial no mezclar la refactorización con la implementación de nuevas funcionalidades; deben ser procesos separados.
- Volver atrás durante una refactorización puede complicar aún más el proceso y generar confusión.
Estrategia gradual
- Se recomienda realizar refactores pequeños y manejables para evitar perder claridad sobre los objetivos del proyecto.
- Anotar mejoras potenciales en herramientas como Trello o Notion para abordarlas después de completar la refactorización actual.
Deuda técnica y su gestión
Concepto de deuda técnica
- La deuda técnica se refiere a los compromisos realizados en el diseño que distancian del enfoque ideal debido a parches aplicados por falta de comprensión del dominio.
- Cuanto mayor sea la deuda técnica, más difícil será implementar cambios futuros debido a parches acumulativos.
Intereses asociados a la deuda técnica
- Al igual que una deuda financiera, la deuda técnica genera "intereses" en forma de complicaciones adicionales al intentar realizar cambios posteriores.
Identificación y resolución de problemas
Detección mediante Code Smells
- Los "Code Smells" son indicios visuales en el código que reflejan fallas en el diseño original y pueden señalar áreas problemáticas.
Análisis crítico necesario
- No todos los Code Smells indican errores críticos; requieren análisis para determinar si realmente representan un problema significativo.