Pregunta

¿Qué es el principio de inversión de dependencia y por qué es importante?

¿Fue útil?

Solución

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

Básicamente dice:

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

En cuanto a 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, DIP reduce el acoplamiento entre diferentes fragmentos de código.La idea es que, aunque hay muchas formas de implementar, digamos, una función de registro, la forma en que se utilizarí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 amplía ese mecanismo de registro.

Al hacer que la implementación dependa también 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 resultar interesante.

Otros consejos

Los libros Desarrollo, principios, patrones y prácticas de software ágil y Principios, patrones y prácticas ágiles en C# son los mejores recursos para comprender plenamente los objetivos y motivaciones originales detrás del principio de inversión de dependencia.El artículo "El principio de inversión de la dependencia" también es un buen recurso, pero debido al hecho de que es una versión condensada de un borrador que eventualmente apareció en los libros mencionados anteriormente, omite algunas discusiones importantes sobre el concepto de propiedad del paquete y de la interfaz, que son clave para distinguir este principio del consejo más general de "programar en una interfaz, no en una implementación" que se encuentra en el libro Design Patterns (Gamma, et.Alabama).

Para proporcionar un resumen, el principio de inversión de dependencia se trata principalmente de revertir la dirección convencional de dependencias de componentes de "nivel superior" a componentes de "nivel inferior", de modo que los componentes de "nivel inferior" dependen de las interfaces propiedad por 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 es reducido tanto como es desplazada desde componentes que son teóricamente menos valiosos hasta componentes que son teóricamente más valiosos.

Esto se logra diseñando componentes cuyas dependencias externas se expresan 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 utiliza el componente (p. ej."Necesito algo", no "Hago algo").

A lo que no se refiere el Principio de Inversión de Dependencia es a la simple práctica de abstraer dependencias mediante el uso de interfaces (p. ej.MiServicio → [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 (p. ej.[MiServicio → IMyServiceLogger] ⇐ Registrador.

La importancia del principio de inversión de dependencia se puede resumir en el 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 la reutilización, podemos delimitar dos subtipos de reutilización:

  1. Usar un componente de software dentro de múltiples aplicaciones con implementaciones de subdependencia (p. ej.Ha desarrollado un contenedor DI y desea proporcionar registros, pero no desea acoplar su contenedor a un registrador específico de modo que todos los que usen su contenedor también tengan que usar la biblioteca de registros elegida.

  2. Utilizar componentes de software dentro de un contexto en evolución (p. ej.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.

En el primer caso de reutilización de componentes en múltiples aplicaciones, como con una biblioteca de infraestructura, el objetivo es proporcionar una infraestructura central necesaria a sus consumidores sin acoplarlos a subdependencias de su propia biblioteca, ya que tomar dependencias de dichas dependencias requiere su los consumidores también requieran las mismas dependencias.Esto puede resultar problemático cuando los consumidores de su biblioteca optan por utilizar una biblioteca diferente para las mismas necesidades de infraestructura (p. ej.NLog vs.log4net), o si eligen utilizar una versión posterior de la biblioteca requerida que no es compatible con la versión requerida por su biblioteca.

En 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 cifrado, etc.).Idealmente, cambiar los detalles de implementación de una aplicación no debería romper los componentes que encapsulan la lógica empresarial de la aplicación.

Nota:Algunos pueden oponerse a describir este segundo caso como reutilización real, razonando que componentes como los componentes de lógica de negocios utilizados dentro de una única aplicación en evolución representan sólo un uso único.La idea aquí, sin embargo, es que cada cambio en los detalles de implementación de la aplicación genere un nuevo contexto y, por lo tanto, un caso de uso diferente, aunque los objetivos finales podrían distinguirse como aislamiento versus aislamiento.portabilidad.

Si bien seguir el principio de inversión de dependencia en este segundo caso puede ofrecer algún beneficio, cabe señalar que su valor aplicado a lenguajes modernos como Java y C# es muy reducido, tal vez hasta el punto de ser irrelevante.Como se analizó 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, simplemente utilizar interfaces definidas en términos del dominio empresarial 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 en última instancia están dentro del mismo paquete.Esta parte del principio refleja aspectos que eran pertinentes al lenguaje en cuestión cuando se codificó el principio (es decir,C++) que no son relevantes para lenguajes más nuevos.Dicho esto, la importancia del principio de inversión de dependencia radica principalmente en el desarrollo de bibliotecas/componentes de software reutilizables.

Puede encontrar una discusión más extensa sobre este principio en relación con el uso simple de interfaces, la inyección de dependencia y el patrón de interfaz separada. aquí.Además, se puede encontrar una discusión sobre cómo se relaciona el principio con los lenguajes tipados dinámicamente como JavaScript. aquí.

Cuando diseñamos aplicaciones de software podemos considerar las clases de bajo nivel, las clases que implementan operaciones básicas y primarias (acceso al disco, protocolos de red,...) y las clases de alto nivel, las clases que encapsulan lógica compleja (flujos de negocio,...).

Estos últimos se basan en las clases de nivel bajo.Una forma natural de implementar tales estructuras sería escribir clases de bajo nivel y, una vez que las tengamos, escribir clases complejas de alto nivel.Dado que las clases de alto nivel se definen en términos de otras, esta parece 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 deberían depender de 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, decidir 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 módulos de nivel superior.

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 normalmente 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 configuración real. implementación detalles de ese módulo.Y eso, evidentemente, no es nada bueno.Pero esto no es un problema en los lenguajes más modernos que se usan comúnmente hoy en día.

Los módulos de alto nivel son inherentemente menos reutilizables que los módulos de bajo nivel porque los primeros normalmente son 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 muy (¿completamente?) específico de la aplicación.Intentar reutilizar un componente de este tipo en una aplicación diferente es contraproducente y sólo puede conducir a un exceso de ingeniería.

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) sólo se puede realizar si el componente A realmente será útil para su reutilización en diferentes aplicaciones o contextos.Si ese no es el caso, aplicar DIP sería un mal diseño.

La inversión de dependencias 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 interfaz de usuario de arquitectura en capas dependía de la capa empresarial y ésta, a su vez, dependía de la capa de acceso a datos.

http://xurxodev.com/content/images/2016/02/Traditional-Layered.png

Tienes que entender la capa, el paquete o la biblioteca.Veamos cómo quedarí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 capa de biblioteca o paquete 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 módulos de bajo nivel.Ambos deberían depender de abstracciones.

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

¿Qué son los módulos de alto nivel y de bajo nivel?Pensando en módulos como bibliotecas o paquetes, los módulos de alto nivel serían aquellos que tradicionalmente tienen dependencias y de bajo nivel de los que 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 que se puede extraer de este principio es que no debería haber dependencia entre concreciones, pero sí debe haber dependencia de una abstracción.Pero según el enfoque que adoptemos podemos estar aplicando mal la dependencia de la inversión, aunque sea una abstracción.

Imaginemos 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 capa de biblioteca o paquete 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.

http://xurxodev.com/content/images/2016/02/Traditional-Layered.png

Para obtener la inversión de dependencia, la interfaz de persistencia debe definirse en el módulo o paquete donde se encuentra 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 la capa de persistencia depende del dominio, llegando a invertir ahora si se define una dependencia.

// Persistence.dll
public class ProductDAO : IProductRepository{

}

http://xurxodev.com/content/images/2016/02/Dependency-Inversion-Layers.png

Profundizando el principio

Es importante asimilar bien el concepto, profundizando en el propósito y beneficios.Si nos quedamos mecánicamente y aprendemos el típico repositorio de casos, 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?

tan comunmente permite que las cosas más estables, que no dependen de cosas menos estables, cambien con mayor 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 existe sólo este ejemplo de repositorio.Hay muchos escenarios donde se aplica este principio y existen arquitecturas basadas en este principio.

Arquitecturas

Hay arquitecturas donde la inversión de dependencias 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 paquetes o bibliotecas.

Arquitectura limpia

En Arquitectura limpia el dominio se ubica en el centro y si se mira en la dirección de las flechas que indican dependencia, queda claro cuáles son las capas más importantes y estables.Las capas exteriores se consideran herramientas inestables, así que evite depender de ellas.

Arquitectura hexagonal

Sucede lo mismo con la arquitectura hexagonal, donde el dominio también se ubica en la parte central y los puertos son abstracciones de comunicación del dominó hacia afuera.Aquí nuevamente es evidente que el dominio es el más estable y la dependencia tradicional se invierte.

Básicamente dice:

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

Una forma mucho más clara de enunciar el Principio de Inversión de Dependencia es:

Sus módulos que encapsulan lógica empresarial compleja no deberían depender directamente de otros módulos que encapsulan lógica empresarial.En cambio, deberían depender únicamente de interfaces con datos simples.

Es decir, en lugar de implementar tu clase Logic como 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
    }
}

Data y DataFromDependency debe vivir en el mismo módulo que Logic, no con Dependency.

¿Por qué hacer esto?

  1. Los dos módulos de lógica empresarial ahora están desacoplados.Cuando Dependency cambios, no es necesario cambiar Logic.
  2. Entendiendo que Logic lo que hace es una tarea mucho más sencilla:sólo funciona con lo que parece un ADT.
  3. Logic ahora se puede probar más fácilmente.Ahora puedes crear una instancia directamente Data con datos falsos y pasarlos.No hay necesidad de simulacros ni estructuras de prueba complejas.

Otros aquí ya dan buenas respuestas y buenos ejemplos.

La razón ADEREZO es importante porque garantiza el principio OO de "diseño débilmente acoplado".

Los objetos en su software NO deben entrar en una jerarquía donde algunos objetos sean los de nivel superior, dependiendo de los objetos de bajo nivel.Los cambios en los objetos de bajo nivel se extenderán a los objetos de nivel superior, lo que hace que el software sea muy frágil ante los cambios.

Quiere que sus objetos de 'nivel superior' sean muy estables y no frágiles ante el cambio, por lo tanto, necesita invertir las dependencias.

Inversión de control (IoC) es un patrón de diseño en el que un marco externo le entrega a un objeto su dependencia, en lugar de solicitarle a un marco su dependencia.

Ejemplo de pseudocódigo usando 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 del COI son:

  • No tiene dependencia de un marco central, por lo que esto se puede cambiar si lo desea.
  • Dado que los objetos se crean mediante inyección, preferiblemente usando interfaces, es fácil crear pruebas unitarias que reemplacen las dependencias con versiones simuladas.
  • Desacoplamiento del código.

El objetivo de la inversión de dependencia es crear software reutilizable.

La idea es que, en lugar de que dos fragmentos de código dependan uno del otro, dependan de alguna interfaz abstracta.Luego podrás reutilizar cualquiera de las piezas sin la otra.

La forma más común de lograr esto 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 Service 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 IoC estableciera 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 tienes que arrastrar las dependencias de MyService, y las dependencias de esas dependencias, y...Bueno, ya captas la idea.

Inversión de contenedores de control y patrón de inyección de dependencia de Martin Fowler también es una buena lectura.encontré Primero los patrones de diseño Un libro fantástico para mi primera incursión en el aprendizaje de DI y otros patrones.

Inversión de dependencia:Depende de abstracciones, no de concreciones.

Inversión de control:Main vs Abstracción, y cómo Main es el pegamento de los sistemas.

DIP and 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-deacoplado-peers/

https://coderstower.com/2019/04/09/inversion-of-control-putting-all-together/

El principio de inversión de dependencia (DIP) dice que

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

ii) Las abstracciones nunca deben depender de detalles.Los detalles deberían depender de 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 interfaz o clases abstractas, no de detalles específicos (implementación de interfaz).

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top