Recientemente he estado leyendo un libro titulado Programación funcional en C # y se me ocurre que la naturaleza inmutable y sin estado de la programación funcional logra resultados similares a los patrones de inyección de dependencia y es posiblemente un mejor enfoque, especialmente en lo que respecta a las pruebas unitarias.
Le agradecería que alguien que tenga experiencia con ambos enfoques pueda compartir sus pensamientos y experiencias para responder a la pregunta principal: ¿ es la programación funcional una alternativa viable a los patrones de inyección de dependencia?
Respuestas:
La gestión de dependencias es un gran problema en OOP por las siguientes dos razones:
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:
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:
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:
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: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.fuente
System.String
. Un sistema de módulos le permitiría reemplazarloSystem.String
con una variable para que la elección de la implementación de la cadena no esté codificada, sino que aún se resuelva en el momento de la compilación.Esto me parece una pregunta extraña. Los enfoques de programación funcional son en gran medida tangenciales a la inyección de dependencia.
Claro, tener un estado inmutable puede empujarlo a no "hacer trampa" al tener efectos secundarios o usar el estado de clase como un contrato implícito entre funciones. Hace que el paso de datos sea más explícito, lo que supongo es la forma más básica de inyección de dependencia. Y el concepto de programación funcional de pasar funciones hace que sea mucho más fácil.
Pero no elimina las dependencias. Sus operaciones aún necesitan todos los datos / operaciones que necesitaban cuando su estado era mutable. Y todavía necesita obtener esas dependencias allí de alguna manera. Por lo tanto, no diría que los enfoques de programación funcional reemplazan a la DI, por lo que no hay ningún tipo de alternativa.
En todo caso, acaban de mostrarle lo mal que el código OO puede crear dependencias implícitas de lo que los programadores rara vez piensan.
fuente
La respuesta rápida a su pregunta es: n .
Pero como otros han afirmado, la pregunta se casa con dos conceptos, algo no relacionados.
Hagamos esto paso a paso.
DI da como resultado un estilo no funcional
En el núcleo de la programación de funciones hay funciones puras: funciones que asignan entrada a salida, por lo que siempre obtienes la misma salida para una entrada determinada.
DI generalmente significa que su unidad ya no es pura ya que la salida puede variar dependiendo de la inyección. Por ejemplo, en la siguiente función:
getBookedSeatCount
(una función) puede variar produciendo diferentes resultados para la misma entrada dada. Esto también lo hacebookSeats
impuro.Hay excepciones para esto: puede inyectar uno de los dos algoritmos de clasificación que implementan el mismo mapeo de entrada-salida, aunque utilizando diferentes algoritmos. Pero estas son excepciones.
Un sistema no puede ser puro
El hecho de que un sistema no puede ser puro se ignora igualmente como se afirma en las fuentes de programación funcional.
Un sistema debe tener efectos secundarios con los ejemplos obvios que son:
Entonces, parte de su sistema debe involucrar efectos secundarios y esa parte también puede involucrar un estilo imperativo o estilo OO.
El paradigma del núcleo del shell
Tomando prestados los términos de la excelente charla de Gary Bernhardt sobre límites , una buena arquitectura de sistema (o módulo) incluirá estas dos capas:
La conclusión clave es 'dividir' el sistema en su parte pura (el núcleo) y la parte impura (el caparazón).
Aunque ofrece una solución (y conclusión) ligeramente defectuosa, este artículo de Mark Seemann propone el mismo concepto. La implementación de Haskell es particularmente perspicaz, ya que muestra que todo se puede hacer con FP.
DI y FP
Emplear DI es perfectamente razonable incluso si la mayor parte de su aplicación es pura. La clave es confinar el DI dentro del caparazón impuro.
Un ejemplo serán los apéndices de API: desea la API real en producción, pero use apéndices en las pruebas. Adherirse al modelo de núcleo de shell ayudará mucho aquí.
Conclusión
Entonces FP y DI no son exactamente alternativas. Es probable que tenga ambos en su sistema, y el consejo es garantizar la separación entre la parte pura e impura del sistema, donde residen FP y DI respectivamente.
fuente
Desde el punto de vista de OOP, las funciones pueden considerarse interfaces de un solo método.
La interfaz es un contrato más fuerte que una función.
Si está utilizando un enfoque funcional y realiza una gran cantidad de DI, en comparación con el uso de un enfoque OOP obtendrá más candidatos para cada dependencia.
vs
fuente