¿Cuál es el principio de inversión de dependencia y por qué es importante?
oop
solid-principles
glossary
principles
dependency-inversion
Phillip Wells
fuente
fuente
Respuestas:
Consulte este documento: El principio de inversión de dependencia .
Básicamente dice:
Resumiendo por qué es importante, en resumen: los cambios son riesgosos y, al depender de un concepto en lugar de una implementación, se reduce la necesidad de cambios en los sitios de llamadas.
Efectivamente, el DIP reduce el acoplamiento entre diferentes piezas de código. La idea es que, aunque existen muchas formas de implementar, por ejemplo, una instalación de registro, la forma en que la usaría debería ser relativamente estable en el tiempo. Si puede extraer una interfaz que represente el concepto de registro, esta interfaz debería ser mucho más estable en el tiempo que su implementación, y los sitios de llamadas deberían verse mucho menos afectados por los cambios que podría realizar mientras mantiene o extiende ese mecanismo de registro.
Al hacer que la implementación también dependa de una interfaz, tiene la posibilidad de elegir en tiempo de ejecución qué implementación se adapta mejor a su entorno particular. Dependiendo de los casos, esto también puede ser interesante.
fuente
Los libros Desarrollo, software, principios y prácticas ágiles de software y Principios, patrones y prácticas ágiles en C # son los mejores recursos para comprender completamente los objetivos y motivaciones originales detrás del Principio de inversión de dependencia. El artículo "El principio de inversión de dependencia" también es un buen recurso, pero debido al hecho de que es una versión condensada de un borrador que finalmente llegó a los libros mencionados anteriormente, deja fuera una discusión importante sobre el concepto de propiedad del paquete y la interfaz, que son clave para distinguir este principio del consejo más general de "programar en una interfaz, no una implementación" que se encuentra en el libro Design Patterns (Gamma, et. al).
Para proporcionar un resumen, el Principio de Inversión de la Dependencia se trata principalmente de invertir la dirección convencional de las dependencias de los componentes de "nivel superior" a los componentes de "nivel inferior" de modo que los componentes de "nivel inferior" dependen de las interfaces que poseen los componentes de "nivel superior". . (Nota: el componente de "nivel superior" aquí se refiere al componente que requiere dependencias / servicios externos, no necesariamente su posición conceptual dentro de una arquitectura en capas). Al hacerlo, el acoplamiento no se reduce tanto como se desplaza de componentes que son teóricamente menos valioso para componentes que son teóricamente más valiosos.
Esto se logra diseñando componentes cuyas dependencias externas se expresen en términos de una interfaz para la cual el consumidor del componente debe proporcionar una implementación. En otras palabras, las interfaces definidas expresan lo que necesita el componente, no cómo se usa el componente (por ejemplo, "INeedSomething", no "IDoSomething").
A lo que no se refiere el Principio de Inversión de Dependencia es a la práctica simple de abstraer dependencias mediante el uso de interfaces (por ejemplo, MyService → [ILogger ⇐ Logger]). Si bien esto desacopla un componente del detalle de implementación específico de la dependencia, no invierte la relación entre el consumidor y la dependencia (por ejemplo, [MyService → IMyServiceLogger] ⇐ Logger.
La importancia del Principio de Inversión de Dependencia se puede resumir en un objetivo singular de poder reutilizar componentes de software que dependen de dependencias externas para una parte de su funcionalidad (registro, validación, etc.)
Dentro de este objetivo general de reutilización, podemos delinear dos subtipos de reutilización:
Uso de un componente de software dentro de múltiples aplicaciones con implementaciones de subdependencia (p. Ej., Ha desarrollado un contenedor DI y desea proporcionar registro, pero no desea acoplar su contenedor a un registrador específico, de modo que todos los que usan su contenedor también deben use su biblioteca de registro elegida).
Uso de componentes de software dentro de un contexto en evolución (por ejemplo, ha desarrollado componentes de lógica empresarial que siguen siendo los mismos en varias versiones de una aplicación donde los detalles de implementación están evolucionando).
Con el primer caso de reutilización de componentes en múltiples aplicaciones, como con una biblioteca de infraestructura, el objetivo es proporcionar una necesidad de infraestructura central a sus consumidores sin acoplar a sus consumidores a las subdependencias de su propia biblioteca, ya que tomar dependencias de tales dependencias requiere su los consumidores también requieren las mismas dependencias. Esto puede ser problemático cuando los consumidores de su biblioteca eligen usar una biblioteca diferente para las mismas necesidades de infraestructura (por ejemplo, NLog vs. log4net), o si eligen usar una versión posterior de la biblioteca requerida que no es compatible con la versión requerido por su biblioteca.
Con el segundo caso de reutilización de componentes de lógica empresarial (es decir, "componentes de nivel superior"), el objetivo es aislar la implementación del dominio central de su aplicación de las necesidades cambiantes de los detalles de su implementación (es decir, cambiar / actualizar bibliotecas de persistencia, bibliotecas de mensajería , estrategias de encriptación, etc.). Idealmente, cambiar los detalles de implementación de una aplicación no debería romper los componentes que encapsulan la lógica de negocios de la aplicación.
Nota: Algunos pueden oponerse a describir este segundo caso como reutilización real, razonando que los componentes como los componentes de lógica de negocios utilizados dentro de una sola aplicación en evolución representan un solo uso. Sin embargo, la idea aquí es que cada cambio en los detalles de implementación de la aplicación genera un nuevo contexto y, por lo tanto, un caso de uso diferente, aunque los objetivos finales podrían distinguirse como aislamiento frente a portabilidad.
Si bien seguir el Principio de inversión de dependencia en este segundo caso puede ofrecer algún beneficio, debe tenerse en cuenta que su valor aplicado a lenguajes modernos como Java y C # es muy reducido, tal vez hasta el punto de ser irrelevante. Como se discutió anteriormente, el DIP implica separar completamente los detalles de implementación en paquetes separados. Sin embargo, en el caso de una aplicación en evolución, el simple uso de interfaces definidas en términos del dominio comercial evitará la necesidad de modificar componentes de nivel superior debido a las necesidades cambiantes de los componentes de detalle de implementación, incluso si los detalles de implementación finalmente residen en el mismo paquete . Esta parte del principio refleja aspectos que eran pertinentes al lenguaje en vista cuando se codificó el principio (es decir, C ++) que no son relevantes para los lenguajes más nuevos. Dicho eso
Una discusión más larga de este principio en lo que se refiere al uso simple de interfaces, inyección de dependencias y el patrón de interfaz separada se puede encontrar aquí . Además, aquí se puede encontrar una discusión sobre cómo se relaciona el principio con lenguajes de tipo dinámico como JavaScript .
fuente
Cuando diseñamos aplicaciones de software, podemos considerar las clases de bajo nivel, las clases que implementan operaciones básicas y primarias (acceso a disco, protocolos de red, ...) y las clases de alto nivel, las clases que encapsulan la lógica compleja (flujos comerciales, ...).
Los últimos dependen de las clases de bajo nivel. Una forma natural de implementar tales estructuras sería escribir clases de bajo nivel y una vez que tengamos que escribir las clases complejas de alto nivel. Dado que las clases de alto nivel se definen en términos de otras, esta parece ser la forma lógica de hacerlo. Pero este no es un diseño flexible. ¿Qué sucede si necesitamos reemplazar una clase de bajo nivel?
El principio de inversión de dependencia establece que:
Este principio busca "invertir" la noción convencional de que los módulos de alto nivel en el software deberían depender de los módulos de nivel inferior. Aquí los módulos de alto nivel poseen la abstracción (por ejemplo, la decisión de los métodos de la interfaz) que implementan los módulos de nivel inferior. De este modo, los módulos de nivel inferior dependen de los módulos de nivel superior.
fuente
La inversión de dependencia bien aplicada brinda flexibilidad y estabilidad a nivel de toda la arquitectura de su aplicación. Permitirá que su aplicación evolucione de manera más segura y estable.
Arquitectura tradicional en capas
Tradicionalmente, una IU de arquitectura en capas dependía de la capa empresarial y esto a su vez dependía de la capa de acceso a datos.
Debe comprender la capa, el paquete o la biblioteca. Veamos cómo sería el código.
Tendríamos una biblioteca o paquete para la capa de acceso a datos.
Y otra lógica empresarial de la capa de paquete o biblioteca que depende de la capa de acceso a datos.
Arquitectura en capas con inversión de dependencia
La inversión de dependencia indica lo siguiente:
¿Cuáles son los módulos de alto nivel y bajo nivel? Pensando en módulos como bibliotecas o paquetes, los módulos de alto nivel serían aquellos que tradicionalmente tienen dependencias y bajo nivel del cual dependen.
En otras palabras, el nivel alto del módulo sería donde se invoca la acción y el nivel bajo donde se realiza la acción.
Una conclusión razonable de este principio es que no debe haber dependencia entre concreciones, pero debe existir una dependencia de una abstracción. Pero de acuerdo con el enfoque que adoptemos, podemos estar aplicando mal la inversión dependiendo de la dependencia, pero una abstracción.
Imagine que adaptamos nuestro código de la siguiente manera:
Tendríamos una biblioteca o paquete para la capa de acceso a datos que define la abstracción.
Y otra lógica empresarial de la capa de paquete o biblioteca que depende de la capa de acceso a datos.
Aunque dependemos de una abstracción, la dependencia entre el negocio y el acceso a los datos sigue siendo la misma.
Para obtener la inversión de dependencia, la interfaz de persistencia debe definirse en el módulo o paquete donde está esta lógica o dominio de alto nivel y no en el módulo de bajo nivel.
Primero defina qué es la capa de dominio y la abstracción de su comunicación se define como persistencia.
Después de que la capa de persistencia depende del dominio, puede invertir ahora si se define una dependencia.
(fuente: xurxodev.com )
Profundizando el principio
Es importante asimilar bien el concepto, profundizando el propósito y los beneficios. Si permanecemos mecánicamente y aprendemos el depósito de casos típico, no podremos identificar dónde podemos aplicar el principio de dependencia.
Pero, ¿por qué invertimos una dependencia? ¿Cuál es el objetivo principal más allá de ejemplos específicos?
Tal comúnmente permite que las cosas más estables, que no dependen de cosas menos estables, cambien con más frecuencia.
Es más fácil cambiar el tipo de persistencia, ya sea la base de datos o la tecnología, para acceder a la misma base de datos que la lógica del dominio o las acciones diseñadas para comunicarse con persistencia. Debido a esto, la dependencia se invierte porque es más fácil cambiar la persistencia si se produce este cambio. De esta forma no tendremos que cambiar el dominio. La capa de dominio es la más estable de todas, por lo que no debería depender de nada.
Pero no solo existe este ejemplo de repositorio. Hay muchos escenarios en los que se aplica este principio y hay arquitecturas basadas en este principio.
Arquitecturas
Hay arquitecturas donde la inversión de dependencia es clave para su definición. En todos los dominios es el más importante y son las abstracciones las que indicarán el protocolo de comunicación entre el dominio y el resto de los paquetes o bibliotecas definidos.
Arquitectura limpia
En la arquitectura limpia, el dominio se encuentra en el centro y si observa en la dirección de las flechas que indican dependencia, está claro cuáles son las capas más importantes y estables. Las capas externas se consideran herramientas inestables, así que evite depender de ellas.
(fuente: 8thlight.com )
Arquitectura Hexagonal
Sucede de la misma manera con la arquitectura hexagonal, donde el dominio también se encuentra en la parte central y los puertos son abstracciones de la comunicación desde el dominó hacia afuera. Aquí nuevamente es evidente que el dominio es el más estable y la dependencia tradicional está invertida.
fuente
Para mí, el principio de inversión de dependencia, como se describe en el artículo oficial , es realmente un intento equivocado de aumentar la reutilización de módulos que son inherentemente menos reutilizables, así como una forma de solucionar un problema en el lenguaje C ++.
El problema en C ++ es que los archivos de encabezado generalmente contienen declaraciones de campos y métodos privados. Por lo tanto, si un módulo C ++ de alto nivel incluye el archivo de encabezado para un módulo de bajo nivel, dependerá de la implementación real detalles de de ese módulo. Y eso, obviamente, no es algo bueno. Pero esto no es un problema en los idiomas más modernos que se usan comúnmente en la actualidad.
Los módulos de alto nivel son inherentemente menos reutilizables que los módulos de bajo nivel porque los primeros son normalmente más específicos de la aplicación / contexto que los segundos. Por ejemplo, un componente que implementa una pantalla de interfaz de usuario es del más alto nivel y también es muy (¿completamente?) Específico para la aplicación. Tratar de reutilizar dicho componente en una aplicación diferente es contraproducente y solo puede conducir a una ingeniería excesiva.
Por lo tanto, la creación de una abstracción separada en el mismo nivel de un componente A que depende de un componente B (que no depende de A) solo se puede hacer si el componente A realmente será útil para su reutilización en diferentes aplicaciones o contextos. Si ese no es el caso, entonces aplicar DIP sería un mal diseño.
fuente
Básicamente dice:
La clase debe depender de abstracciones (por ejemplo, interfaz, clases abstractas), no detalles específicos (implementaciones).
fuente
Buenas respuestas y buenos ejemplos ya han sido dados por otros aquí.
La razón por la que DIP es importante es porque garantiza el principio de OO de "diseño débilmente acoplado".
Los objetos en su software NO deben entrar en una jerarquía donde algunos objetos son los de nivel superior, que dependen de objetos de bajo nivel. Los cambios en los objetos de bajo nivel se trasladarán a los objetos de nivel superior, lo que hace que el software sea muy frágil para el cambio.
Desea que sus objetos de "nivel superior" sean muy estables y no frágiles para el cambio, por lo tanto, debe invertir las dependencias.
fuente
Una forma mucho más clara de establecer el Principio de inversión de dependencia es:
Sus módulos que encapsulan la lógica empresarial compleja no deberían depender directamente de otros módulos que encapsulan la lógica empresarial. En cambio, deberían depender solo de interfaces para datos simples.
Es decir, en lugar de implementar su clase
Logic
como suele hacer la gente:deberías hacer algo como:
Data
yDataFromDependency
debería vivir en el mismo módulo queLogic
, no conDependency
.¿Por qué hacer esto?
Dependency
cambia, no necesita cambiarLogic
.Logic
hace es una tarea mucho más simple: funciona solo en lo que parece un ADT.Logic
ahora se puede probar más fácilmente. Ahora puede crear instancias directamenteData
con datos falsos y pasarlos. No necesita simulacros ni andamios de prueba complejos.fuente
DataFromDependency
, que hace referencia directamenteDependency
, está en el mismo módulo queLogic
, entonces elLogic
módulo aún depende directamente delDependency
módulo en tiempo de compilación. Según la explicación del tío Bob del principio , evitar eso es todo el punto de DIP. Por el contrario, para seguir DIP,Data
debe estar en el mismo módulo queLogic
, peroDataFromDependency
debe estar en el mismo módulo queDependency
.Inversión de control (IoC) es un patrón de diseño en el que un marco externo transfiere su dependencia a un objeto, en lugar de pedirle a un marco su dependencia.
Ejemplo de pseudocódigo con búsqueda tradicional:
Código similar usando IoC:
Los beneficios de IoC son:
fuente
El punto de inversión de dependencia es hacer software reutilizable.
La idea es que, en lugar de dos piezas de código que dependan entre sí, confíen en alguna interfaz abstracta. Luego puede reutilizar cualquier pieza sin la otra.
La forma en que esto se logra más comúnmente es a través de un contenedor de inversión de control (IoC) como Spring en Java. En este modelo, las propiedades de los objetos se configuran a través de una configuración XML en lugar de que los objetos salgan y encuentren su dependencia.
Imagina este pseudocódigo ...
MyClass depende directamente tanto de la clase de servicio como de la clase ServiceLocator. Necesita ambos si desea usarlo en otra aplicación. Ahora imagina esto ...
Ahora, MyClass se basa en una única interfaz, la interfaz IService. Dejaríamos que el contenedor de IoC realmente establezca el valor de esa variable.
Ahora, MyClass se puede reutilizar fácilmente en otros proyectos, sin traer consigo la dependencia de esas otras dos clases.
Aún mejor, no tiene que arrastrar las dependencias de MyService, y las dependencias de esas dependencias, y ... bueno, ya tiene la idea.
fuente
Si podemos dar por sentado que a un empleado de "alto nivel" en una corporación se le paga por la ejecución de sus planes, y que estos planes se entregan mediante la ejecución agregada de muchos planes de empleados de "bajo nivel", entonces podríamos decir generalmente es un plan terrible si la descripción del plan del empleado de alto nivel de alguna manera se combina con el plan específico de cualquier empleado de nivel inferior.
Si un ejecutivo de alto nivel tiene un plan para "mejorar el tiempo de entrega", e indica que un empleado en la línea de envío debe tomar café y hacer estiramientos cada mañana, entonces ese plan está altamente acoplado y tiene poca cohesión. Pero si el plan no menciona a ningún empleado específico y, de hecho, simplemente requiere que "una entidad que pueda realizar el trabajo esté preparada para trabajar", entonces el plan está débilmente acoplado y es más coherente: los planes no se superponen y pueden sustituirse fácilmente . Los contratistas, o robots, pueden reemplazar fácilmente a los empleados y el plan de alto nivel permanece sin cambios.
"Alto nivel" en el principio de inversión de dependencia significa "más importante".
fuente
Puedo ver que se ha dado una buena explicación en las respuestas anteriores. Sin embargo, quiero proporcionar una explicación fácil con un ejemplo simple.
El principio de inversión de dependencias permite al programador eliminar las dependencias codificadas para que la aplicación se acople de forma flexible y se pueda ampliar.
Cómo lograr esto: través de la abstracción
Sin inversión de dependencia:
En el fragmento de código anterior, el objeto de dirección está codificado. En cambio, si podemos usar la inversión de dependencia e inyectar el objeto de la dirección pasando por el método de constructor o de establecimiento. Veamos.
Con inversión de dependencia:
fuente
Inversión de dependencia: depende de abstracciones, no de concreciones.
Inversión de control: Principal vs Abstracción, y cómo Principal es el pegamento de los sistemas.
Estas son algunas buenas publicaciones que hablan de esto:
https://coderstower.com/2019/03/26/dependency-inversion-why-you-shouldnt-avoid-it/
https://coderstower.com/2019/04/02/main-and-abstraction-the-decoupled-peers/
https://coderstower.com/2019/04/09/inversion-of-control-putting-all-together/
fuente
Digamos que tenemos dos clases:
Engineer
yProgrammer
:El ingeniero de clase depende de la clase de programador, como a continuación:
En este ejemplo, la clase
Engineer
depende de nuestraProgrammer
clase. ¿Qué pasará si necesito cambiar elProgrammer
?Obviamente necesito cambiar el
Engineer
también. (Wow, en este punto tambiénOCP
se viola)Entonces, ¿qué tenemos que limpiar este desastre? La respuesta es la abstracción en realidad. Por abstracción, podemos eliminar la dependencia entre estas dos clases. Por ejemplo, puedo crear una
Interface
para la clase Programador y a partir de ahora cada clase que quiera usarProgrammer
tiene que usar suInterface
, Luego, al cambiar la clase Programador, no necesitamos cambiar ninguna clase que la haya usado, Debido a la abstracción que tenemos usado.Nota:
DependencyInjection
puede ayudarnos a hacerDIP
ySRP
también.fuente
Además de la gran cantidad de respuestas generalmente buenas, me gustaría agregar una pequeña muestra mía para demostrar las buenas y las malas prácticas. Y sí, ¡no soy de los que arrojan piedras!
Digamos que desea un pequeño programa para convertir una cadena en formato base64 a través de E / S de consola. Aquí está el enfoque ingenuo:
El DIP básicamente dice que los componentes de alto nivel no deberían depender de la implementación de bajo nivel, donde "nivel" es la distancia de E / S según Robert C. Martin ("Arquitectura limpia"). Pero, ¿cómo salir de esta situación? Simplemente haciendo que el codificador central dependa solo de las interfaces sin molestarse en cómo se implementan:
Tenga en cuenta que no necesita tocar
GoodEncoder
para cambiar el modo de E / S: esa clase está contenta con las interfaces de E / S que conoce; cualquier implementación de bajo nivelIReadable
yIWriteable
nunca lo molestará.fuente
GoodEncoder
en su segundo ejemplo. Para crear un ejemplo DIP, debe introducir una noción de lo que "posee" las interfaces que ha extraído aquí, y, en particular, ponerlas en el mismo paquete que GoodEncoder mientras sus implementaciones permanecen fuera.El principio de inversión de dependencia (DIP) dice que
i) Los módulos de alto nivel no deberían depender de módulos de bajo nivel. Ambos deberían depender de abstracciones.
ii) Las abstracciones nunca deberían depender de los detalles. Los detalles deben depender de las abstracciones.
Ejemplo:
Nota: La clase debe depender de abstracciones como la interfaz o clases abstractas, no detalles específicos (implementación de la interfaz).
fuente