5  Inversión de control, refactoring, inyección de dependencias

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.