En el artículo anterior analizamos el principio de Segregación de Interfaces. En este quinto y último artículo abarcaremos la "D" de Dependency Inversion Principle ¡vamos!

Historia

El principio de Inversión de dependencias fue postulado a mediados de los noventa por nuestro conocido Robert C. Martin (Uncle Bob) y descrito en numerosas publicaciones, incluido un artículo que apareció en el Informe de C ++ en mayo de 1996 titulado: The Dependency Inversion Principle.

DIP: Principio de Inversión de Dependencias

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

b) Las abstracciones no deberían depender de los detalles. Son los detalles los que deberían depender de abstracciones.

En primer lugar veamos a qué se refiere con módulos de alto y bajo nivel.

Alto y bajo nivel

Como términos técnicos, alto y bajo nivel son utilizados para clasificar, describir objetivos específicos de una operación sistemática.

Alto nivel describe aquellas operaciones que son de naturaleza más abstracta; en donde los objetivos generales suelen estar más relacionados con el sistema macro más amplio en su conjunto.

Bajo nivel describe componentes individuales más específicos, centrándose en los detalles de micro funciones rudimentarias en lugar de procesos macro y complejos.

Imagina que eres el CEO de una multimillonaria compañía de gaseosas. Como CEO tendrás un conjunto de responsabilidades. Por ejemplo, decidir de manera estratégica el rumbo de la compañía, ser la cara pública, guiar recursos humanos. Este tipo de tareas vendrían a ser de alto nivel. Como CEO seguramente no te encargarías de conducir camiones, repartir mercancía o empaquetar gaseosas, lo cual vendrían a ser tareas de bajo nivel.

Módulos de alto y bajo nivel

Un módulo no es más que una parte de nuestro programa. Los módulos de alto nivel representan la identidad de la aplicación, son ellos los que toman decisiones importantes basados en el propósito de la aplicación.

Por otro lado, los módulos de bajo nivel contienen implementaciones detalladas acerca de los procesos individuales que se necesitan para cumplir con el propósito macro.

"Los módulos de alto nivel no deberían depender de los de bajo nivel"

En otras palabras, que las cosas importantes no dependan de cosas menos importantes.

Las dependencias representan un riesgo. Hacernos cargo de ese riesgo tiene sus costos. Cuando lo que tiene mayor relevancia depende de los menos relevante, estamos en problemas. Lo más relevante se verá obligado a cambiar constantemente ante cualquier cambio introducido.

Solución a la dependencia directa

Un posible camino para evitar acople entre módulos de distinto nivel de jerarquía es usar abstracciones. Cada uno de estos módulos en los distintos niveles deben usar una interfaz abstracta, implementada por el próximo módulo en un nivel inferior.

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

Los componentes de alto nivel deberían comunicarse con los de bajo nivel por medio de una interfaz en vez de comunicarse directamente con componentes concretos. A su vez, los componentes de bajo nivel deben implementar estas interfaces. Deben estar diseñados para encajar en estas interfaces. Por eso se dice que dependen de la interfaz, porque estas son quienes definirán como serán diseñados. De este modo los componentes en los distintos niveles dependerán de la abstracción, pero de manera distinta.

b) Las abstracciones no deberían depender de los detalles. Son los detalles los que deberían depender de abstracciones.

Si los detalles cambian no deberían afectar la abstracción. La abstracción es el modo en el que los clientes ven el objeto. Lo que suceda dentro del objeto no debería ser importante para los clientes.

Por ejemplo, los pedales, el volante y la palanca de cambios son abstracciones de lo que sucede dentro del motor. Sin embargo, no dependen de los detalles porque si alguien cambia el motor antiguo por uno nuevo, aún debería poder conducir el automóvil sin saber que el motor cambió.

Desde otra perspectiva, los detalles deben ajustarse a lo que dice la abstracción. No querríamos implementar un motor cuyos frenos hagan que la velocidad del auto se duplique. La idea es poder implementar los frenos de la forma que queramos, siempre que externamente se comporten de la misma manera.

Ejemplo

Empecemos con estas dos clases: Boton la cual recibe un mensaje presionar() que determina si el usuario lo ha presionado o no. Y una clase Lampara la cual recibe los mensajes encender() y apagar().

 

Inconvenientes con este diseño
  • Boton depende directamente en LamparaSi Lampara cambia, Boton cambiará también.
  • Boton no es reusable: No podrías usar presionar() para encender una Lavadora, por ejemplo.

Este diseño es una violación al principio de Inversión de Dependencias, puesto en las palabras de Uncle Bob:

  • La política de alto nivel de la aplicación no ha sido separada de la implementación de bajo nivel.
    • La política de alto nivel de la aplicación es la abstracción subyacente de la aplicación, la verdadera esencia del sistema, su propósito. En este caso sería la detección de los gestos encendido y apagado, no importa cómo lo implementemos, este es el propósito. Como podemos ver tenemos una instancia de Lampara dentro de Boton.
  • Las abstracciones no han sido separadas de los detalles
    • Boton "conoce" como encender() y apagar() la Lampara, por ende es una abstracción que depende de los detalles de Lampara.
  • La política de alto nivel automáticamente depende de los módulos de bajo nivel, las abstracciones automáticamente dependen en los detalles.
    • El propósito de la aplicación es la detección de los gestos de encendido y apagado, en este caso esto depende de conocer los detalles de Lampara, lo cual automáticamente obliga a las abstracciones a conocer los detalles.

Solución

Creemos una capa intermedia en donde definiremos una interfaz abstracta asociada a Boton e implementada por cualquier clase como Lampara.

 

De este modo:
  • Boton depende únicamente de abstracciones, puede ser reusado en varias clases que implementen Conmutable. Por ejemplo, LavadoraVentilador
  • Cambios en Lampara no afectarán a Boton.
  • ¡Las dependencias han sido invertidas! ahora Lampara tiene que adaptarse a la interfaz definida por Boton. Lo cual tiene sentido, ya que la idea fundamental es que lo más importante no dependa de lo menos importante.

Conclusión

Este principio en lo personal se me dificultó entenderlo. Más que todo por la cantidad de conceptos que dan vueltas. Fundamentalmente este principio busca que desarrollemos código desacoplado, código que podamos reutilizar y escalar sin complicaciones. Al aplicar DIP estamos permitiendo que las cosas que cambian más (las implementaciones concretas) dependan de las cosas que cambian menos (las abstracciones), haciendo nuestro sistema más resistente a los cambios y es esa la verdadera razón por la cual queremos en la medida de lo posible depender de abstracciones. Evitamos así que los cambios en esas partes más inestables se propaguen hacia el resto del sistema, ya que el resto del sistema no depende de ellas.

Referencias

Juan María Hernandez. La inversión de dependencias no es (sólo) lo que tú piensas. Recuperado de http://blog.koalite.com/2015/04/la-inversion-de-dependencias-no-es-solo-lo-que-tu-piensas/

Gabriella's Journey. (6 de diciembre de 2017). DIP: The Dependency-Inversion Principle. Recuperado de https://medium.com/@gabriellamedas/dip-the-dependency-inversion-principle-dbe0f784f3aa

Robert C. Martin, (25 de octubre de 2002), Agile Software Development, Principles, Patterns and Practices, Upper Saddle River, New Jersey, Pearson Education, Inc.

http://stg-tud.github.io/sedc/Lecture/ws13-14/3.5-DIP.html#mode=document

 

Mandanos tus sugerencias

Ayudanos con ideas para los artículos de este blog a contacto@somospnt.com

¡Seguínos en nuestras redes sociales para enterarte de los últimos posts!