Inversión de Dependencias (DIP)

Miguel Ángel Sánchez
All you need is Clean Code
4 min readSep 12, 2017
¿Soldarías una lámpara directamente al tendido eléctrico? ¿O usarías un enchufe?

Por fin llegamos al último post de la serie de principios SOLID, y para finalizarla, vamos a hablar del que considero uno de los más fundamentales.

Personalmente este principio me enseñó una manera diferente de hacer las cosas a como las había estado haciendo antes. A partir de comprender el DIP, el testing automático se convirtió en un paseo, la reutilización de código se volvió una realidad mantenible y la refactorización un hecho.

¿Y en qué consiste tal maravilla? Te estarás preguntando…

Básicamente consiste en una forma de desacoplar módulos de software, basándonos en dos pequeñas reglas:

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

Veamos a continuación un ejemplo que no cumple con el DIP:

En este caso tenemos dos repositorios: UserRepository que usa una conexión de base de datos MySQL para acceder a sus datos y PostRepository, que usa una conexión PostgreSQL para el mismo caso.

Dicho en otras palabras, tenemos dos implementaciones de una misma abstracción: el acceso a base de datos, y nuestros repositorios están fuertemente acoplados al tipo concreto que usan. Si el día de mañana esta situación cambiase tendríamos que editar todos los constructores y las instanciaciones de estos repositorios, lo cual puede llegar a ser un problema bastante grande.

Visto de una forma más mundana: si tenemos una lámpara que necesita alimentación eléctrica… soldamos la lampara al tendido eléctrico? o usamos un enchufe? La respuesta es obvia.

Veamos ahora un ejemplo del código anterior refactorizado para que cumpla con el DIP:

Qué ha cambiado?

  • Hemos extraído a una interfaz común los rasgos propios de los dos accesos a base de datos.
  • Hemos hecho que ambas conexiones implementen esta interfaz.
  • Hemos hecho que los repositorios dependan ahora de la abstracción, no de las clases concretas.

Sencillo verdad?

Si el día de mañana la situación cambia, solo hay que implementar la nueva clase concreta de la abstracción y usarla en el constructor de nuestros repositorios.

Ahora bien, sigue siendo bastante tedioso tener que cambiar TODAS y cada una de las instanciaciones que hemos hecho de nuestros repositorios para que usen la nueva dependencia.

Inyección de dependencias

Con el anterior cambio, las instanciaciones quedarían de la siguiente forma:

Estas clases tienen pinta de que van a ser muy utilizadas, por lo que instanciarlas cada vez va a ser tedioso, y en caso de que cambie la forma de hacerlo, problemático.

Para solucionar este problema existe un patrón llamado Dependency Injection, según el cual las dependencias de nuestro código no deben de instanciarse en nuestro código, sino que estas deben ser inyectadas en el mismo. Esta “inyección” se puede hacer de dos maneras:

  • A través del constructor.
  • A través de setters.

Siempre es más recomendada la inyección a través de constructor que de a través de setters, ya que esta segunda forma crea un acoplamiento temporal hasta que la dependencia es inyectada.

Para simplificar más aún esto, este patrón hace uso de un container o service locator que nos permite definir cómo se construyen los objetos y bajo que nombre queremos poder acceder a ellos y él se encarga del resto. Muy a grosso modo es como un array que contiene todas las posibles dependencias.

Cada framework tiene su propio contenedor de dependencias: Symfony, Laravel, Phalcon… además hay otros que se pueden usar independientemente del framework, como Pimple, y los hay que incluso caben en un twit, como Twittee:

class Container {
protected $s=array();
function __set($k, $c) { $this->s[$k]=$c; }
function __get($k) { return $this->s[$k]($this); }
}

La mayoría de estos inyectores de dependencias son muy configurables y su uso resulta muy sencillo, solo hay que echar un vistazo a la documentación de los mismos para familiarizarnos con ellos.

Un ejemplo muy tosco de cómo quedaría el código anterior usando un contenedor de dependencias sería este:

El container mostrado en este ejemplo es una versión muy sencilla y poco escalable de lo que sería un container, pero sirve para entender su funcionamiento:

  • Se inicializa el container con los servicios necesarios (lo ideal es que esta inicialización sea dinámica, bien a través de leer una configuración o de cualquier otro método).
  • Se accede a los servicios que hemos definido mediante un método accesor.
  • Si no existe este método, se lanza una excepción.

La cosa puede ponerse peliaguda cuando hay servicios que dependen de otros servicios (como en el caso de los repositorios, que son un servicio que dependen de un servicio), por lo que hay que asegurarse de algún modo que los servicios se instancian en el orden adecuado (algo que no se ve en el código del ejemplo).

Las ventajas con este patrón son obvias:

  • La construcción de las dependencias queda delegada a una única clase: el Container
  • Si la construcción de un servicio o dependencia cambia, solo hay que cambiar la definición del servicio o la forma en que se construye el servicio en el container.

Esta es ya el último post de la serie de principios SOLID, espero que os hayan gustado. Para más información os recomiendo echar un ojo a los siguientes libros:

Saludos!

--

--

Miguel Ángel Sánchez
All you need is Clean Code

PHP Developer - Clean Coder | Founder @secture_com 🏴‍☠️ | Previously: Tech Lead @Beroomers 🏡 · CTO @origo_by 🌀