¿Cuál es el principio de inversión de dependencia y por qué es importante?

178

¿Cuál es el principio de inversión de dependencia y por qué es importante?

Phillip Wells
fuente
3
Cantidad ridícula de respuestas aquí usando los términos "alto nivel" y "bajo nivel" de Wikipedia. Estos términos son inaccesibles y llevan a muchos lectores a esta página. Si va a regurgitar wikipedia, ¡ defina estos términos en sus respuestas para dar contexto!
8bitjunkie

Respuestas:

107

Consulte este documento: El principio de inversión de dependencia .

Básicamente dice:

  • Los módulos de alto nivel no deberían depender de los módulos de bajo nivel. Ambos deberían depender de abstracciones.
  • Las abstracciones nunca deberían depender de los detalles. Los detalles deben depender de las abstracciones.

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.

Carl Seleborg
fuente
31
Esta respuesta no dice por qué DIP es importante, ni siquiera qué es DIP. He leído el documento DIP oficial y creo que este es realmente un "principio" pobre e injustificado, porque se basa en una suposición errónea: que los módulos de alto nivel son reutilizables.
Rogério
3
Considere un gráfico de dependencia para algunos objetos. Aplicar DIP a los objetos. Ahora cualquier objeto será independiente de la implementación de los otros objetos. Las pruebas unitarias ahora son simples. Posterior refactorización para su reutilización es posible. Los cambios de diseño tienen ámbitos de cambio muy limitados. Los problemas de diseño no caen en cascada. Consulte también el patrón AI "Pizarra" para la inversión de la dependencia de datos. En conjunto, herramientas muy poderosas para hacer que el software sea comprensible, mantenible y confiable. Ignorar la inyección de dependencia en este contexto. No está relacionado.
Tim Williscroft
66
Una clase A que usa una clase B no significa que A "depende" de la implementación de B; depende de la interfaz pública de B solamente. Agregar una abstracción separada de la que ahora dependen A y B solo significa que A ya no tendrá una dependencia en tiempo de compilación de la interfaz pública de B. El aislamiento entre unidades se puede lograr fácilmente sin estas abstracciones adicionales; existen herramientas de burla específicas para eso, en Java y .NET, que se ocupan de todas las situaciones (métodos estáticos, constructores, etc.). La aplicación de DIP tiende a hacer que el software sea más complejo y menos mantenible, y no más comprobable.
Rogério
1
Entonces, ¿qué es la inversión de control? una forma de lograr la inversión de dependencia?
user20358
2
@Rogerio, mira la respuesta de Derek Greer a continuación, él lo explica. AFAIK, DIP dice que es A quien exige lo que A necesita, y no B quien dice lo que A necesita. Entonces, la interfaz que A necesita no debe ser dada por B, sino por A.
ejaenv
145

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:

  1. 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).

  2. 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 .

Derek Greer
fuente
17
Gracias. Ahora veo cómo mi respuesta pierde el punto. La diferencia entre MyService → [ILogger ⇐ Logger] y [MyService → IMyServiceLogger] ⇐ Logger es sutil pero importante.
Patrick McElhaney el
2
En la misma línea se explica muy bien aquí: lostechies.com/derickbailey/2011/09/22/…
ejaenv
1
Cómo deseaba que la gente dejara de usar registradores como el caso de uso canónico para la inyección de dependencia. Especialmente en relación con log4net, donde es casi un anti patrón. Aparte de eso, ¡explicación descarada!
Casper Leon Nielsen
44
@ Casper Leon Nielsen - DIP no tiene nada que ver con DI No son sinónimos ni conceptos equivalentes.
TSmith
44
@ VF1 Como se indicó en el párrafo de resumen, la importancia del Principio de Inversión de Dependencia es principalmente con la reutilización. Si John libera una biblioteca que tiene una dependencia externa en una biblioteca de registro y Sam desea utilizar la biblioteca de John, Sam asume la dependencia de registro transitorio. Sam nunca podrá implementar su aplicación sin la biblioteca de registro que John seleccionó. Sin embargo, si John sigue el DIP, Sam es libre de proporcionar un adaptador y usar la biblioteca de registro que elija. El DIP no se trata de conveniencia, sino de acoplamiento.
Derek Greer el
14

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:

  • Los módulos de alto nivel no deberían depender de módulos de bajo nivel. Ambos deberían depender de abstracciones.
  • Las abstracciones no deberían depender de los detalles. Los detalles deben depender de las abstracciones.

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.

nikhil.singhal
fuente
11

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.

// DataAccessLayer.dll
public class ProductDAO {

}

Y otra lógica empresarial de la capa de paquete o biblioteca que depende de la capa de acceso a datos.

// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO { 
    private ProductDAO productDAO;
}

Arquitectura en capas con inversión de dependencia

La inversión de dependencia indica lo siguiente:

Los módulos de alto nivel no deberían depender de los módulos de bajo nivel. Ambos deberían depender de abstracciones.

Las abstracciones no deberían depender de los detalles. Los detalles deben depender de las abstracciones.

¿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.

// DataAccessLayer.dll
public interface IProductDAO
public class ProductDAO : IProductDAO{

}

Y otra lógica empresarial de la capa de paquete o biblioteca que depende de la capa de acceso a datos.

// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO { 
    private IProductDAO productDAO;
}

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.

// Domain.dll
public interface IProductRepository;

using DataAccessLayer;
public class ProductBO { 
    private IProductRepository productRepository;
}

Después de que la capa de persistencia depende del dominio, puede invertir ahora si se define una dependencia.

// Persistence.dll
public class ProductDAO : IProductRepository{

}


(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.

xurxodev
fuente
No estoy seguro de lo que significa decir, pero esta frase es incoherente: "Pero de acuerdo con el enfoque que adoptemos, podemos estar aplicando mal la dependencia de la inversión, pero una abstracción". ¿Quizás te gustaría arreglarlo?
Mark Amery
9

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.

Rogério
fuente
Incluso si la abstracción de alto nivel es útil solo en el contexto de esa aplicación, puede tener valor cuando desea reemplazar la implementación real con un código auxiliar para probar (o proporcionar una interfaz de línea de comandos para una aplicación generalmente disponible en la web)
Przemek Pokrywka
3
DIP es importante en todos los lenguajes y no tiene nada que ver con C ++ en sí. Incluso si su código de alto nivel nunca abandona su aplicación, DIP le permite contener el cambio de código cuando agrega o cambia el código de nivel inferior. Esto reduce los costos de mantenimiento y las consecuencias imprevistas del cambio. DIP es un concepto de nivel superior. Si no lo comprende, debe buscar más en Google.
Dirk Bester
3
Creo que entendiste mal lo que dije sobre C ++. Fue solo una motivación para DIP; sin duda es más general que eso. Tenga en cuenta que el artículo oficial sobre DIP deja en claro que la motivación central era apoyar la reutilización de los módulos de alto nivel, al hacerlos inmunes a los cambios en los módulos de bajo nivel; sin necesidad de reutilización, es muy probable que sea excesivo y de ingeniería excesiva. (¿Lo leyó? También habla sobre el problema de C ++.)
Rogério
1
¿No estás pasando por alto la aplicación inversa de DIP? Es decir, los módulos de nivel superior implementan su aplicación, esencialmente. Su principal preocupación no es reutilizarlo, es hacerlo menos dependiente de las implementaciones de módulos de nivel inferior para que actualizarlo para mantenerse al día con los cambios futuros aísle su aplicación de los estragos del tiempo y las nuevas tecnologías. No es para facilitar la sustitución de WindowsOS, es hacer que WindowsOS sea menos dependiente de los detalles de implementación de FAT / HDD para que los NTFS / SSD más nuevos se puedan conectar a WindowsOS con poco o ningún impacto.
knockNrod
1
La pantalla de la interfaz de usuario definitivamente no es el módulo de nivel más alto o no debería ser para ninguna aplicación. Los módulos de nivel más alto son los que contienen reglas de negocio. Un módulo de nivel más alto no está definido por su potencial de reutilización tampoco. Consulte la explicación simple del tío Bob en este artículo: blog.cleancoder.com/uncle-bob/2016/01/04/…
humbaba
8

Básicamente dice:

La clase debe depender de abstracciones (por ejemplo, interfaz, clases abstractas), no detalles específicos (implementaciones).

martin.ra
fuente
¿Puede ser así de simple? ¿Hay artículos largos e incluso libros como DerekGreer mencionó? En realidad estaba buscando una respuesta simple, pero es increíble si es así de simple: D
Darius.V
1
@ darius-v no es otra versión de decir "usar abstracciones". Se trata de quién posee las interfaces. El director dice que el cliente (componentes de nivel superior) debe definir interfaces y que los componentes de nivel inferior deben implementarlas.
boran el
6

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.

Hace
fuente
66
¿Cómo hace exactamente eso DIP para el código Java o .NET? En esos lenguajes, a diferencia de C ++, los cambios en la implementación de un módulo de bajo nivel no requieren cambios en los módulos de alto nivel que lo usan. Solo los cambios en la interfaz pública se trasladarían, pero luego la abstracción definida en el nivel superior también tendría que cambiar.
Rogério
5

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 Logiccomo suele hacer la gente:

class Dependency { ... }
class Logic {
    private Dependency dep;
    int doSomething() {
        // Business logic using dep here
    }
}

deberías hacer algo como:

class Dependency { ... }
interface Data { ... }
class DataFromDependency implements Data {
    private Dependency dep;
    ...
}
class Logic {
    int doSomething(Data data) {
        // compute something with data
    }
}

Datay DataFromDependencydebería vivir en el mismo módulo que Logic, no con Dependency.

¿Por qué hacer esto?

  1. Los dos módulos de lógica de negocios ahora están desacoplados. Cuando Dependencycambia, no necesita cambiar Logic.
  2. Comprender lo que Logichace es una tarea mucho más simple: funciona solo en lo que parece un ADT.
  3. Logicahora se puede probar más fácilmente. Ahora puede crear instancias directamente Datacon datos falsos y pasarlos. No necesita simulacros ni andamios de prueba complejos.
mattvonb
fuente
No creo que esto sea correcto. Si DataFromDependency, que hace referencia directamente Dependency, está en el mismo módulo que Logic, entonces el Logicmódulo aún depende directamente del Dependencymó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, Datadebe estar en el mismo módulo que Logic, pero DataFromDependencydebe estar en el mismo módulo que Dependency.
Mark Amery
3

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:

class Service {
    Database database;
    init() {
        database = FrameworkSingleton.getService("database");
    }
}

Código similar usando IoC:

class Service {
    Database database;
    init(database) {
        this.database = database;
    }
}

Los beneficios de IoC son:

  • No depende de un marco central, por lo que puede cambiarlo si lo desea.
  • Dado que los objetos se crean por inyección, preferiblemente usando interfaces, es fácil crear pruebas unitarias que reemplacen las dependencias con versiones simuladas.
  • Desacoplamiento del código.
Staale
fuente
¿El principio de inversión de dependencia y la inversión de control son lo mismo?
Peter Mortensen el
3
La "inversión" en DIP no es la misma que en Inversión de control. El primero trata sobre dependencias en tiempo de compilación, mientras que el segundo trata sobre el flujo de control entre métodos en tiempo de ejecución.
Rogério
Siento que a la versión de IoC le falta algo. ¿Cómo se define el objeto de la base de datos? ¿De dónde viene? Estoy tratando de entender cómo esto no solo retrasa la especificación de todas las dependencias al nivel superior en una dependencia de un dios gigante
Richard Tingle
1
Como ya dijo @ Rogério, DIP no es DI / IoC. Esta respuesta es incorrecta OMI
zeraDev
1

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 ...

public class MyClass
{
  public Service myService = ServiceLocator.service;
}

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 ...

public class MyClass
{
  public IService myService;
}

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.

Marc Hughes
fuente
2
Esta respuesta realmente no se trata de DIP, sino de otra cosa. Dos piezas de código pueden confiar en alguna interfaz abstraída y no una en la otra, y aún no siguen el Principio de Inversión de Dependencia.
Rogério
1

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".

Miguel
fuente
1
Esta es una analogía notablemente buena. Al igual que, bajo el DIC, los componentes de alto nivel aún dependen en tiempo de ejecución de los de bajo nivel, en esta analogía, la ejecución de los planes de alto nivel depende de los planes de bajo nivel. Pero también, así como bajo DIC son los planes de bajo nivel los que dependen en tiempo de compilación de los planes de alto nivel, es el caso en su analogía de que la formación de los planes de bajo nivel depende de los planes de alto nivel.
Mark Amery
0

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:

 class Student {
    private Address address;

    public Student() {
        this.address = new Address();
    }
}
class Address{
    private String perminentAddress;
    private String currentAdrress;

    public Address() {
    }
} 

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:

class Student{
    private Address address;

    public Student(Address address) {
        this.address = address;
    }
    //or
    public void setAddress(Address address) {
        this.address = address;
    }
}
Sumanth Varada
fuente
-1

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.

DIP y IoC

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/

Daniel Andres Peláez López
fuente
-1

Digamos que tenemos dos clases: Engineery Programmer:

El ingeniero de clase depende de la clase de programador, como a continuación:

class Engineer () {

    fun startWork(programmer: Programmer){
        programmer.work()
    }
}

class Programmer {

    fun work(){
        //TODO Do some work here!
    }
}

En este ejemplo, la clase Engineerdepende de nuestra Programmerclase. ¿Qué pasará si necesito cambiar el Programmer?

Obviamente necesito cambiar el Engineertambién. (Wow, en este punto también OCPse 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 Interfacepara la clase Programador y a partir de ahora cada clase que quiera usar Programmertiene 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: DependencyInjectionpuede ayudarnos a hacer DIPy SRPtambién.

Ehsan
fuente
-1

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:

class Program
{
    static void Main(string[] args)
    {
        /*
         * BadEncoder: High-level class *contains* low-level I/O functionality.
         * Hence, you'll have to fiddle with BadEncoder whenever you want to change
         * the I/O mode or details. Not good. A good encoder should be I/O-agnostic --
         * problems with I/O shouldn't break the encoder!
         */
        BadEncoder.Run();            
    }
}

public static class BadEncoder
{
    public static void Run()
    {
        Console.WriteLine(Convert.ToBase64String(Encoding.UTF8.GetBytes(Console.ReadLine())));
    }
}    

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:

class Program
{
    static void Main(string[] args)
    {           
        /* Demo of the Dependency Inversion Principle (= "High-level functionality
         * should not depend upon low-level implementations"): 
         * You can easily implement new I/O methods like
         * ConsoleReader, ConsoleWriter without ever touching the high-level
         * Encoder class!!!
         */            
        GoodEncoder.Run(new ConsoleReader(), new ConsoleWriter());        }
}

public static class GoodEncoder
{
    public static void Run(IReadable input, IWriteable output)
    {
        output.WriteOutput(Convert.ToBase64String(Encoding.ASCII.GetBytes(input.ReadInput())));
    }
}

public interface IReadable
{
    string ReadInput();
}

public interface IWriteable
{
    void WriteOutput(string txt);
}

public class ConsoleReader : IReadable
{
    public string ReadInput()
    {
        return Console.ReadLine();
    }
}

public class ConsoleWriter : IWriteable
{
    public void WriteOutput(string txt)
    {
        Console.WriteLine(txt);
    }
}

Tenga en cuenta que no necesita tocar GoodEncoderpara cambiar el modo de E / S: esa clase está contenta con las interfaces de E / S que conoce; cualquier implementación de bajo nivel IReadabley IWriteablenunca lo molestará.

John Silence
fuente
No creo que esto sea inversión de dependencia. Después de todo, no ha invertido ninguna dependencia aquí; su lector y escritor no dependen GoodEncoderen 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.
Mark Amery
-2

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:

    public interface ICustomer
    {
        string GetCustomerNameById(int id);
    }

    public class Customer : ICustomer
    {
        //ctor
        public Customer(){}

        public string GetCustomerNameById(int id)
        {
            return "Dummy Customer Name";
        }
    }

    public class CustomerFactory
    {
        public static ICustomer GetCustomerData()
        {
            return new Customer();
        }
    }

    public class CustomerBLL
    {
        ICustomer _customer;
        public CustomerBLL()
        {
            _customer = CustomerFactory.GetCustomerData();
        }

        public string GetCustomerNameById(int id)
        {
            return _customer.GetCustomerNameById(id);
        }
    }

    public class Program
    {
        static void Main()
        {
            CustomerBLL customerBLL = new CustomerBLL();
            int customerId = 25;
            string customerName = customerBLL.GetCustomerNameById(customerId);


            Console.WriteLine(customerName);
            Console.ReadKey();
        }
    }

Nota: La clase debe depender de abstracciones como la interfaz o clases abstractas, no detalles específicos (implementación de la interfaz).

Rejwanul Reja
fuente