Principio de inversión de la dependencia
En el diseño orientado a objetos, el principio de inversión de dependencia es una forma específica de desacoplar módulos de software. Al seguir este principio, las relaciones de dependencia convencionales establecidas desde los módulos de alto nivel de establecimiento de políticas a los módulos de dependencia de bajo nivel se invierten, lo que hace que los módulos de alto nivel sean independientes de los detalles de implementación del módulo de bajo nivel. El principio establece:[1]
- Los módulos de alto nivel no deberían depender de los módulos de bajo nivel. Ambos deberían depender de abstracciones (p.ej., interfaces).
- Las abstracciones no deberían depender de los detalles. Los detalles (implementaciones concretas) deben depender de 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 1 y 2 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 el/los esquema/s de interacción abstracta descubierto/s entre dos módulos es/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 tradicionales
editarEn la arquitectura de aplicación convencional, los componentes de nivel inferior (p. Ej. La capa de utilidad) está diseñada para ser consumida por componentes de nivel superior (p. Ej. Capa de política) 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 las capas superiores/políticas.
Patrón de inversión de dependencia
editarCon la adición de una capa abstracta, las capas de nivel superior e inferior reducen las dependencias tradicionales de arriba abajo. Sin embargo, el concepto de "inversión" no significa que las capas de nivel inferior dependan de las capas de nivel superior. Ambas capas deben depender de abstracciones que dibujen el comportamiento que necesitan las capas de nivel superior.
En una aplicación directa de inversión de dependencia, los resúmenes son propiedad de las capas superiores / políticas. Esta arquitectura agrupa los componentes superiores / de política y las abstracciones que definen los servicios inferiores juntos en el mismo paquete. Las capas de nivel inferior se crean por herencia / 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/políticas. Las capas superiores podrían usar 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 común que un Adaptador medie entre los servicios y las abstracciones.
Generalización del patrón de inversión de dependencia
editarEn muchos proyectos, el principio y el patrón de inversión de dependencia se consideran un concepto único que debe generalizarse, es decir, aplicarse a todas las interfaces entre módulos de software. Hay al menos dos razones para eso:
- Es más simple 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 hecho el trabajo de abstracción".
- Debido a que muchas herramientas de pruebas unitarias dependen de la herencia para lograr la burla, el uso de interfaces genéricas entre clases (no solo entre módulos cuando tiene sentido usar la generalidad) se convirtió en la regla.
Si la herramienta de burla utilizada se basa solo en la herencia, puede ser necesario aplicar ampliamente el patrón de inversión de dependencia. Esto tiene grandes inconvenientes:
- La simple implementación de una interfaz sobre una clase no es suficiente para reducir el acoplamiento; Solo pensar en la abstracción potencial de las interacciones puede conducir a un diseño menos acoplado.
- La implementación de interfaces genéricas en todas partes en 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 es generalmente: solo simulacros.
- La generalización de la interfaz requiere más código de plomería, en particular las fábricas que generalmente dependen de un marco de inyección de dependencia.
- La generalización de la interfaz también restringe el uso del lenguaje de programación.
Restricciones de generalización
editarLa 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 :
- Todas las variables miembro en una clase deben ser interfaces o resúmenes.
- Todos los paquetes de clase concretos deben conectarse solo a través de la interfaz o paquetes de clase abstractos.
- Ninguna clase debe derivar de una clase concreta.
- Ningún método debe anular un método implementado.[1]
- Toda instanciación variable requiere la implementación de un patrón de creación, como el método de fábrica o el patrón de fábrica, o el uso de un marco de inyección de dependencia .
Restricciones de burla de la interfaz
editarEl uso de herramientas de burla basadas en herencia también introduce restricciones:
- Los miembros estáticos visibles desde el exterior deben confiar sistemáticamente en la inyección de dependencia, lo que los hace mucho más difíciles de implementar.
- Todos los métodos comprobables deberían convertirse en una implementación de interfaz o una anulación de una definición abstracta.
Direcciones futuras
editarLos principios son formas de pensar, los patrones son formas comunes de resolver problemas. Los patrones de codificación pueden verse como características faltantes del lenguaje de programación.
- Los lenguajes de programación continuarán evolucionando para permitir el cumplimiento de contratos de uso más fuertes y más precisos, en al menos dos direcciones: hacer cumplir las condiciones de uso (condiciones pre, post e invariantes) e interfaces basadas en estado. Esto probablemente fomentará y potencialmente simplificará una aplicación más fuerte del patrón de inversión de dependencia en muchas situaciones.
- Más y más herramientas de burla ahora usan la inyección de dependencia para resolver el problema de reemplazar miembros estáticos y no virtuales. El lenguaje de programación probablemente evolucionará para generar un código de bytes compatible con burlas. Una dirección será restringir el uso de miembros no virtuales, la otra será generar, al menos en situaciones de prueba, un código de bytes que permita la burla basada en la no herencia.
Implementaciones
editarDos implementaciones comunes de DIP utilizan una arquitectura lógica similar, con diferentes implicaciones.
Una implementación directa empaqueta las clases de políticas con clases de resúmenes de servicio en una biblioteca. En esta implementación, los componentes de alto nivel y los componentes de bajo nivel se distribuyen en paquetes / bibliotecas separadas, donde las interfaces que definen el comportamiento / servicios requeridos por el componente de alto nivel son propiedad y existen dentro de la biblioteca del componente de alto nivel. La implementación de la interfaz del componente de alto nivel por parte del componente de bajo nivel requiere que el paquete de componentes 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 el 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 dependencia se puede elegir para maximizar la reutilización del código de política y eliminar las dependencias cíclicas.
En esta versión de DIP, la dependencia del componente de la capa inferior de las interfaces/resúmenes en las capas de nivel superior 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 lo opuesto, 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
editarMódulo genealógico
editarUn sistema genealógico puede representar las relaciones entre las personas como un gráfico de las relaciones directas entre ellas (padre-hijo, padre-hija, madre-hijo, madre-hija, esposo-esposa, esposa-esposo, etc. ) Esto es muy eficiente y extensible, ya que es fácil agregar un exesposo 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 (incluidos o no hermanastros o hermanastros), abuelos, primos, etc.
Dependiendo del uso del módulo genealógico, presentar relaciones comunes como propiedades directas distintas (ocultar 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 le permita a uno cambiar completamente la representación interna de las relaciones directas. sin ningún efecto en los módulos que los usan. También permite incorporar definiciones exactas de hermanos o tíos en el módulo genealógico, aplicando así 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 mostrar que una implementación de relación más especializada y más 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
editarImagine que tiene que implementar un cliente en un servidor de archivos remoto (FTP, almacenamiento en la nube...) Puede considerarlo como un conjunto de interfaces abstractas:
- Conexión/desconexión (puede ser necesaria una capa de persistencia de conexión)
- Interfaz de carpeta/creación de etiquetas/renombrar/eliminar/listar
- Interfaz de creación/reemplazo/cambio de nombre/eliminación/lectura de archivos
- Búsqueda de archivos
- Reemplazo concurrente o resolución de borrado
- Gestión del historial de archivos...
Si tanto los archivos locales como los archivos remotos ofrecen las mismas interfaces abstractas, cualquier módulo de alto nivel que use archivos locales e implemente completamente el patrón de inversión de dependencia podrá acceder indiscriminadamente a los archivos locales y remotos.
El disco local generalmente usará una carpeta, el almacenamiento remoto puede usar carpetas y / o etiquetas. Tienes que decidir cómo unificarlos si es posible.
En el archivo remoto, es posible que tengamos que usar solo crear o reemplazar: la actualización de archivos remotos no necesariamente tiene sentido porque la actualización aleatoria es demasiado lenta comparando la actualización aleatoria del archivo local y puede ser muy complicado de implementar). En el archivo remoto, es posible que necesitemos lectura y escritura parciales (al menos dentro del módulo de archivo remoto para permitir que se reanude la descarga o carga después de una interrupción de la comunicación), pero la lectura aleatoria no está adaptada (excepto si se usa un caché local).
La búsqueda de archivos puede ser conectable : la búsqueda de archivos puede depender del sistema operativo o, en particular, la búsqueda de etiquetas o de texto completo, implementarse con sistemas distintos (sistema operativo integrado o disponible por separado).
La detección simultánea de resolución de eliminación o reemplazo puede afectar las otras interfaces abstractas.
Al diseñar el cliente del servidor de archivos remoto para cada interfaz conceptual, debe preguntarse a sí mismo el nivel de servicio que requieren sus módulos de alto nivel (no necesariamente todos) y no solo cómo implementar las funcionalidades del servidor de archivos remoto sino también cómo hacer el archivo servicios en su aplicación compatibles entre servicios de archivos ya implementados (archivos locales, clientes en la nube existentes) y su nuevo cliente de servidor de archivos remoto.
Una vez que haya diseñado las interfaces abstractas requeridas, su cliente de servidor de archivos remoto debe implementar estas interfaces. Y debido a que probablemente restringió algunas funcionalidades locales existentes en el archivo local (por ejemplo, la actualización de archivos), es posible que deba escribir adaptadores para módulos de acceso a archivos remotos locales u otros existentes que ofrecen las mismas interfaces abstractas. También debe escribir su propio enumerador de acceso a archivos que le permita recuperar todos los sistemas compatibles con archivos disponibles y configurados en su computadora.
Una vez que lo haga, su aplicación podrá guardar sus documentos de forma local o remota de forma transparente. O más simple, el módulo de alto nivel que usa las nuevas interfaces de acceso a archivos se puede usar indistintamente en escenarios de acceso a archivos locales o remotos, lo que lo hace reutilizable.
Observación : muchos sistemas operativos han comenzado a implementar este tipo de funcionalidades y su trabajo puede estar limitado para adaptar su nuevo cliente a estos modelos ya abstraídos.
En este ejemplo, pensar en el módulo como un conjunto de interfaces abstractas y adaptar otros módulos a este conjunto de interfaces le permite proporcionar una interfaz común para muchos sistemas de almacenamiento de archivos.
Controlador de vista de modelo
editarLos paquetes UI y ApplicationLayer contienen principalmente clases concretas. Los controladores contienen resúmenes/tipos de interfaz. La interfaz de usuario tiene una instancia de ICustomerHandler. Todos los paquetes están físicamente separados. En ApplicationLayer hay una implementación concreta que utilizará la clase Page. Las instancias de esta interfaz son creadas dinámicamente por una Fábrica (posiblemente en el mismo paquete de Controladores). Los tipos concretos, Page y CustomerHandler, no dependen el uno del otro; ambos dependen de ICustomerHandler.
El efecto directo es que la interfaz de usuario no necesita hacer referencia a ApplicationLayer ni a ningún paquete concreto que implemente ICustomerHandler. La clase concreta se cargará usando la reflexión. En cualquier momento, la implementación concreta podría ser reemplazada por otra implementación concreta sin cambiar la clase de IU. Otra posibilidad interesante es que la clase Page implemente una interfaz IPageViewer que podría pasarse como argumento a los métodos ICustomerHandler. Entonces, la implementación concreta podría comunicarse con la interfaz de usuario sin una dependencia concreta. De nuevo, ambos están vinculados por interfaces.
Patrones relacionados
editarLa aplicación del principio de inversión de dependencia también puede verse como un ejemplo del patrón del 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 del adaptador también depende de la abstracción de la interfaz del adaptador (por supuesto, ya que implementa su interfaz), mientras que puede implementarse mediante el uso de código desde su propio módulo de bajo nivel. El nivel alto no depende del módulo de nivel bajo, ya que solo usa el nivel bajo indirectamente a través de la interfaz del adaptador invocando métodos polimórficos a la interfaz implementados por el adaptado y su módulo de nivel bajo.
Se emplean varios patrones, como el complemento, el localizador de servicios o la inyección de dependencia para facilitar el aprovisionamiento en tiempo de ejecución de la implementación del componente de bajo nivel elegido para el componente de alto nivel.
Historia
editarEl principio de inversión de la dependencia fue postulado por Robert C. Martin y se describió en varias publicaciones, incluido el artículo Métricas de calidad de diseño orientado a objetos: un análisis de dependencias,[3] un artículo que apareció en el Informe C ++ en mayo de 1996 titulado El principio de inversión de dependencia,[4] y los libros Desarrollo de software ágil, Principios, patrones y prácticas,[1] y Principios, patrones y prácticas ágiles en C # .
Véase también
editar- Patrón adaptador
- Inyección de dependencia
- Diseño por contrato
- Interfaz
- Inversión de control
- Plug-in (informática)
- Patrón de localizador de servicio
- SOLID – la "D" en "SOLID" representa el principio de inversión de dependencia
- Paradoja del inventor
Referencias
editar- ↑ a b c d e f Martin, Robert C. (2003). Agile Software Development, Principles, Patterns, and Practices. Prentice Hall. pp. 127-131. ISBN 978-0135974445.
- ↑ Freeman, Eric; Freeman, Elisabeth; Kathy, Sierra; Bert, Bates (2004). Hendrickson, Mike, ed. Head First Design Patterns (paperback) 1. O'REILLY. ISBN 978-0-596-00712-6. Consultado el 21 de junio de 2012.
- ↑ Martin, Robert C. (October 1994). «Object Oriented Design Quality Metrics: An analysis of dependencies». Consultado el 15 de octubre de 2016.
- ↑ Martin, Robert C. (May 1996). «The Dependency Inversion Principle». C++ Report. Archivado desde el original el 14 de julio de 2011.
Enlaces externos
editar- Métricas de calidad de diseño orientado a objetos: un análisis de dependencias Robert C. Martin, Informe C ++, septiembre / octubre de 1995
- El principio de inversión de dependencia, Robert C. Martin, Informe C ++, mayo de 1996
- Examinando el principio de inversión de dependencia, Derek Greer
- DIP in the Wild, Brett L. Schuchert, mayo de 2013
- Contenedor IoC para Unity3D - parte 2