stringtranslate.com

Principio de inversión de dependencia

En el diseño orientado a objetos , el principio de inversión de dependencia es una metodología específica para módulos de software acoplados de forma flexible . Al seguir este principio, se invierten las relaciones de dependencia convencionales establecidas desde los módulos de alto nivel que establecen políticas hasta los módulos de bajo nivel que dependen de ellos, lo que hace que los módulos de alto nivel sean independientes de los detalles de implementación de los módulos de bajo nivel. El principio establece: [1]

  1. Los módulos de alto nivel no deberían importar nada de los módulos de bajo nivel. Ambos deberían depender de abstracciones (por ejemplo, interfaces).
  2. Las abstracciones no deberían depender de los detalles. Los detalles (implementaciones concretas) deberían depender de las abstracciones.

Al dictar que tanto los objetos de alto nivel como los de bajo nivel deben depender de la misma abstracción, este principio de diseño invierte la forma en que algunas personas pueden pensar sobre la programación orientada a objetos. [2]

La idea detrás de los puntos A y B de este principio es que al diseñar la interacción entre un módulo de alto nivel y uno de bajo nivel, la interacción debe considerarse como una interacción abstracta entre ellos. Esto no solo tiene implicaciones en el diseño del módulo de alto nivel, sino también en el de bajo nivel: el de bajo nivel debe diseñarse teniendo en cuenta la interacción y puede ser necesario cambiar su interfaz de uso.

En muchos casos, pensar en la interacción en sí misma como un concepto abstracto permite reducir el acoplamiento de los componentes sin introducir patrones de codificación adicionales, permitiendo solo un esquema de interacción más ligero y menos dependiente de la implementación.

Cuando los esquemas de interacción abstracta descubiertos entre dos módulos son genéricos y la generalización tiene sentido, este principio de diseño también conduce al siguiente patrón de codificación de inversión de dependencia.

Patrón de capas tradicional

En la arquitectura de aplicaciones convencional, los componentes de nivel inferior (por ejemplo, la capa de utilidad) están diseñados para ser utilizados por componentes de nivel superior (por ejemplo, la capa de políticas), lo que permite construir sistemas cada vez más complejos. En esta composición, los componentes de nivel superior dependen directamente de los componentes de nivel inferior para lograr alguna tarea. Esta dependencia de los componentes de nivel inferior limita las oportunidades de reutilización de los componentes de nivel superior. [1]

El objetivo del patrón de inversión de dependencia es evitar esta distribución altamente acoplada con la mediación de una capa abstracta y aumentar la reutilización de capas superiores/de políticas.

Patrón de inversión de dependencia

Con la adición de una capa abstracta, tanto las capas de nivel superior como las de nivel inferior reducen las dependencias tradicionales de arriba hacia abajo. Sin embargo, el concepto de "inversión" no significa que las capas de nivel inferior dependan directamente de las capas de nivel superior . Ambas capas deberían depender de abstracciones (interfaces) que expongan el comportamiento que necesitan las capas de nivel superior.

En una aplicación directa de la inversión de dependencias, las abstracciones son propiedad de las capas superiores o de políticas. Esta arquitectura agrupa los componentes superiores o de políticas y las abstracciones que definen los servicios inferiores en el mismo paquete. Las capas inferiores se crean mediante la herencia o implementación de estas clases o interfaces abstractas . [1]

La inversión de las dependencias y la propiedad fomenta la reutilización de las capas superiores o de políticas. Las capas superiores podrían utilizar otras implementaciones de los servicios inferiores. Cuando los componentes de la capa de nivel inferior están cerrados o cuando la aplicación requiere la reutilización de servicios existentes, es habitual que un adaptador medie entre los servicios y las abstracciones.

Generalización del patrón de inversión de dependencia

En muchos proyectos, el principio y el patrón de inversión de dependencias se consideran un concepto único que debería generalizarse, es decir, aplicarse a todas las interfaces entre módulos de software. Existen al menos dos razones para ello:

  1. Es más sencillo ver un buen principio de pensamiento como un patrón de codificación. Una vez que se ha codificado una clase abstracta o una interfaz, el programador puede decir: "He realizado el trabajo de abstracción".
  2. Debido a que muchas herramientas de pruebas unitarias dependen de la herencia para lograr la simulación , el uso de interfaces genéricas entre clases (no solo entre módulos cuando tiene sentido usar generalidad) se convirtió en la regla.

Si la herramienta de simulación utilizada se basa únicamente en la herencia, puede resultar necesario aplicar ampliamente el patrón de inversión de dependencia. Esto tiene importantes inconvenientes:

  1. La simple implementación de una interfaz sobre una clase no es suficiente para reducir el acoplamiento; sólo pensar en la posible abstracción de las interacciones puede conducir a un diseño menos acoplado.
  2. Implementar interfaces genéricas en todas partes de un proyecto hace que sea más difícil de entender y mantener. En cada paso, el lector se preguntará cuáles son las otras implementaciones de esta interfaz y la respuesta generalmente es: solo simulacros.
  3. La generalización de la interfaz requiere más código de plomería, en particular fábricas que generalmente dependen de un marco de inyección de dependencia.
  4. La generalización de la interfaz también restringe el uso del lenguaje de programación.

Restricciones de generalización

La presencia de interfaces para lograr el Patrón de Inversión de Dependencia (DIP) tiene otras implicaciones de diseño en un programa orientado a objetos :

Restricciones de burla de la interfaz

El uso de herramientas de simulación basadas en herencia también introduce restricciones:

Direcciones futuras

Los principios son formas de pensar. Los patrones son formas comunes de resolver problemas. Los patrones de codificación pueden existir para reemplazar las características faltantes del lenguaje de programación.

Implementaciones

Dos implementaciones comunes de DIP utilizan una arquitectura lógica similar pero con diferentes implicaciones.

Una implementación directa empaqueta las clases de políticas con clases abstractas de servicios en una biblioteca. En esta implementación, los componentes de alto nivel y los componentes de bajo nivel se distribuyen en paquetes/bibliotecas independientes, donde las interfaces que definen el comportamiento/los servicios requeridos por el componente de alto nivel son propiedad de la biblioteca del componente de alto nivel y existen dentro de ella. La implementación de la interfaz del componente de alto nivel por parte del componente de bajo nivel requiere que el paquete del componente de bajo nivel dependa del componente de alto nivel para la compilación, invirtiendo así la relación de dependencia convencional.

Las figuras 1 y 2 ilustran código con la misma funcionalidad, sin embargo, en la figura 2 se ha utilizado una interfaz para invertir la dependencia. La dirección de la dependencia se puede elegir para maximizar la reutilización del código de políticas y eliminar las dependencias cíclicas.

En esta versión de DIP, la dependencia de los componentes de la capa inferior respecto de las interfaces/resúmenes de las capas superiores dificulta la reutilización de los componentes de la capa inferior. En cambio, esta implementación "invierte" la dependencia tradicional de arriba hacia abajo a la opuesta, de abajo hacia arriba.

Una solución más flexible extrae los componentes abstractos en un conjunto independiente de paquetes/bibliotecas:

La separación de cada capa en su propio paquete fomenta la reutilización de cualquier capa, proporcionando robustez y movilidad. [1]

Ejemplos

Módulo genealógico

Un sistema genealógico puede representar las relaciones entre personas como un gráfico de relaciones directas entre ellas (padre-hijo, padre-hija, madre-hijo, madre-hija, marido-mujer, esposa-marido, etc.). Esto es muy eficiente y extensible, ya que es fácil agregar un exmarido o un tutor legal.

Pero algunos módulos de nivel superior pueden requerir una forma más sencilla de navegar por el sistema: cualquier persona puede tener hijos, padres, hermanos (incluyendo medios hermanos y hermanas o no), abuelos, primos, etc.

Dependiendo del uso del módulo genealógico, la presentación de relaciones comunes como propiedades directas distintas (ocultando el gráfico) hará que el acoplamiento entre un módulo de nivel superior y el módulo genealógico sea mucho más ligero y permitirá cambiar la representación interna de las relaciones directas por completo sin ningún efecto sobre los módulos que las utilizan. También permite incorporar definiciones exactas de hermanos o tíos en el módulo genealógico, lo que hace cumplir el principio de responsabilidad única .

Finalmente, si el primer enfoque de gráfico generalizado extensible parece el más extensible, el uso del módulo genealógico puede demostrar que una implementación de relación más especializada y simple es suficiente para las aplicaciones y ayuda a crear un sistema más eficiente.

En este ejemplo, abstraer la interacción entre los módulos conduce a una interfaz simplificada del módulo de nivel inferior y puede conducir a una implementación más simple del mismo.

Cliente de servidor de archivos remoto

Un cliente de servidor de archivos remoto (FTP, almacenamiento en la nube...) se puede modelar como un conjunto de interfaces abstractas:

  1. Conexión/Desconexión (puede ser necesaria una capa de persistencia de conexión)
  2. Interfaz de creación/cambio de nombre/eliminación/lista de carpetas/etiquetas
  3. Interfaz de creación/reemplazo/renombrado/eliminación/lectura de archivos
  4. Búsqueda de archivos
  5. Resolución de reemplazo o eliminación concurrente
  6. Gestión del historial de archivos...

Si los archivos locales y remotos ofrecen las mismas interfaces abstractas, los módulos de alto nivel que implementan el patrón de inversión de dependencias pueden utilizarlas indistintamente. La aplicación podrá guardar sus documentos de forma local o remota de forma transparente.

Se debe considerar el nivel de servicio requerido por los módulos de alto nivel.

Diseñar un módulo como un conjunto de interfaces abstractas y adaptar otros módulos a él puede proporcionar una interfaz común para muchos sistemas.

Modelo-vista-controlador

Ejemplo de DIP

Los paquetes UI y ApplicationLayer contienen principalmente clases concretas. Los controladores contienen tipos abstractos/de interfaz. UI tiene una instancia de ICustomerHandler. Todos los paquetes están separados físicamente. En ApplicationLayer hay una implementación concreta de CustomerHandler que utilizará la clase Page. Las instancias de la interfaz ICustomerHandler se crean dinámicamente mediante una Factory (posiblemente en el mismo paquete Controllers). Los tipos concretos Page y CustomerHandler dependen de ICustomerHandler, no entre sí.

Dado que la interfaz de usuario no hace referencia a ApplicationLayer ni a ningún otro paquete concreto que implemente ICustomerHandler, la implementación concreta de CustomerHandler se puede reemplazar sin cambiar la clase de interfaz de usuario. Además, la clase Page implementa la interfaz IPageViewer, que se puede pasar como argumento a los métodos de ICustomerHandler, lo que permite que la implementación concreta de CustomerHandler se comunique con la interfaz de usuario sin una dependencia concreta. Nuevamente, ambas están vinculadas por interfaces.

Patrones relacionados

La aplicación del principio de inversión de dependencias también puede considerarse un ejemplo del patrón adaptador . Es decir, la clase de alto nivel define su propia interfaz de adaptador, que es la abstracción de la que dependen las otras clases de alto nivel. La implementación adaptada también depende necesariamente de la misma abstracción de la interfaz de adaptador, mientras que puede implementarse utilizando código desde dentro de su propio módulo de bajo nivel. El módulo de alto nivel no depende del módulo de bajo nivel, ya que solo utiliza la funcionalidad de bajo nivel indirectamente a través de la interfaz de adaptador invocando métodos polimórficos a la interfaz que son implementados por la implementación adaptada y su módulo de bajo nivel. [ cita requerida ]

Se emplean varios patrones como Plugin , Service Locator , [3] o inyección de dependencia [4] [5] para facilitar el aprovisionamiento en tiempo de ejecución de la implementación del componente de bajo nivel elegido al componente de alto nivel. [ cita requerida ]

Historia

El principio de inversión de dependencia fue postulado por Robert C. Martin y descrito en varias publicaciones, incluido el artículo Object Oriented Design Quality Metrics: an analysis of dependencies , [6] un artículo que apareció en C++ Report en junio de 1996 titulado The Dependency Inversion Principle , [7] y los libros Agile Software Development, Principles, Patterns, and Practices , [1] y Agile Principles, Patterns, and Practices in C# .

Véase también

Referencias

  1. ^ abcdef Martin, Robert C. (2003). Desarrollo ágil de software: principios, patrones y prácticas. Prentice Hall. pp. 127–131. ISBN 978-0135974445.
  2. ^ Freeman, Eric; Freeman, Elisabeth; Kathy, Sierra; Bert, Bates (2004). Hendrickson, Mike; Loukides, Mike (eds.). Patrones de diseño Head First (libro de bolsillo) . Vol. 1. O'REILLY. ISBN 978-0-596-00712-6. Recuperado el 21 de junio de 2012 .
  3. ^ Aung, Nay Lin (1 de diciembre de 2018). "Localizador de servicios frente a inyección de dependencias". Medium . Consultado el 6 de diciembre de 2022 .
  4. ^ Mathews, Sasha (25 de marzo de 2021). "Simplemente estás inyectando una dependencia, pensando que estás siguiendo la inversión de dependencia...". Medium . Consultado el 6 de diciembre de 2022 .
  5. ^ Erez, Guy (9 de marzo de 2022). "Inversión de dependencias frente a inyección de dependencias". Medium . Consultado el 6 de diciembre de 2022 .
  6. ^ Martin, Robert C. (octubre de 1994). "Object Oriented Design Quality Metrics: An analysis of dependencies" (PDF) . Consultado el 15 de octubre de 2016 .
  7. ^ Martin, Robert C. (junio de 1996). "El principio de inversión de dependencias" (PDF) . C++ Report . Vol. 8, núm. 6. págs. 61–66. ISSN  1040-6042. Archivado desde el original (PDF) el 14 de julio de 2011.

Enlaces externos