La gestión de dependencias es un gran problema en OOP por las siguientes dos razones:
- El fuerte acoplamiento de datos y código.
- Uso ubicuo de los efectos secundarios.
La mayoría de los programadores de OO consideran que el acoplamiento estrecho de datos y código es totalmente beneficioso, pero tiene un costo. Administrar el flujo de datos a través de las capas es una parte inevitable de la programación en cualquier paradigma. El acoplamiento de sus datos y código agrega el problema adicional de que si desea utilizar una función en un determinado punto, debe encontrar una manera de llevar su objeto a ese punto.
El uso de efectos secundarios crea dificultades similares. Si usa un efecto secundario para alguna funcionalidad, pero desea poder cambiar su implementación, prácticamente no tiene otra opción que inyectar esa dependencia.
Considere como ejemplo un programa de spammer que raspa las páginas web para direcciones de correo electrónico y luego las envía por correo electrónico. Si tiene una mentalidad DI, en este momento está pensando en los servicios que encapsulará detrás de las interfaces y en qué servicios se inyectarán dónde. Dejaré ese diseño como ejercicio para el lector. Si tiene una mentalidad FP, en este momento está pensando en las entradas y salidas para la capa más baja de funciones, como:
- Ingrese una dirección de página web, envíe el texto de esa página.
- Ingrese el texto de una página, envíe una lista de enlaces desde esa página.
- Ingrese el texto de una página, envíe una lista de direcciones de correo electrónico en esa página.
- Ingrese una lista de direcciones de correo electrónico, envíe una lista de direcciones de correo electrónico con los duplicados eliminados.
- Ingrese una dirección de correo electrónico, envíe un correo electrónico no deseado para esa dirección.
- Ingrese un correo electrónico no deseado, envíe los comandos SMTP para enviar ese correo electrónico.
Cuando piensa en términos de entradas y salidas, no hay dependencias de funciones, solo dependencias de datos. Eso es lo que los hace tan fáciles de realizar pruebas unitarias. Su próxima capa organiza la salida de una función para alimentarla a la entrada de la siguiente, y puede intercambiar fácilmente las diversas implementaciones según sea necesario.
En un sentido muy real, la programación funcional naturalmente lo impulsa a invertir siempre sus dependencias de funciones, y por lo tanto, generalmente no tiene que tomar ninguna medida especial para hacerlo después del hecho. Cuando lo haga, las herramientas como funciones de orden superior, cierres y aplicaciones parciales hacen que sea más fácil lograrlo con menos repetitivo.
Tenga en cuenta que no son las dependencias mismas las que son problemáticas. Son las dependencias las que señalan el camino equivocado. La siguiente capa puede tener una función como:
processText = spamToSMTP . emailAddressToSpam . removeEmailDups . textToEmailAddresses
Está perfectamente bien que esta capa tenga dependencias codificadas de esta manera, porque su único propósito es unir las funciones de la capa inferior. Cambiar una implementación es tan simple como crear una composición diferente:
processTextFancy = spamToSMTP . emailAddressToFancySpam . removeEmailDups . textToEmailAddresses
Esta fácil recomposición es posible por la falta de efectos secundarios. Las funciones de la capa inferior son completamente independientes entre sí. La siguiente capa puede elegir cuál processText
se usa realmente en función de alguna configuración de usuario:
actuallyUsedProcessText = if (config == "Fancy") then processTextFancy else processText
Nuevamente, no es un problema porque todas las dependencias apuntan en una dirección. No necesitamos invertir algunas dependencias para que todas apunten de la misma manera, porque las funciones puras ya nos obligaron a hacerlo.
Tenga en cuenta que puede hacer esto mucho más acoplado pasando config
a la capa más baja en lugar de verificarlo en la parte superior. FP no le impide hacer esto, pero tiende a hacerlo mucho más molesto si lo intenta.