30/09/2019 | Consejos tecnológicos,Tecnologías

Principios S.O.L.I.D. – Dependency Inversion

Single responsibility principle – Principio de responsabilidad única

Open-closed principle – Principio de abierto-cerrado

Liskov substitution principle – Principio de sustitución de Liskov

Interface segregation principle – Principio de segregación de interfaces

Dependency inversion principle – Principio de inversión de dependencias

Continuando con nuestra serie de posts sobre los principios SOLID, hoy veremos el último de ellos pero no por ello menos importante, el principio de inversión de dependencias (DIP).

Este principio especifica cómo deben ser las relaciones entre componentes para evitar el acoplamiento entre los distintos módulos de un software.

La definición dada por Robert C. Martin consiste en dos puntos:

  • 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 detalles (implementaciones concretas). Las detalles deberían depender de abstracciones.

Al aplicar estas reglas el resultado es una arquitectura donde las dependencias están invertidas respecto a la forma tradicional de pensar en la programación orientada a objetos, además reduce el acoplamiento entre los módulos de alto nivel y los módulo de bajo nivel al añadir una clase abstracta entre ambos.

La razón de invertir las dependencias utilizando clases abstractas e interfaces es que estas son más estables que las implementaciones concretas, por lo que si tuviésemos que realizar un cambio en la lógica de nuestra aplicación, aplicaríamos los cambios solamente en la clase afectada sin que esto implique modificaciones en otros módulos.

Un ejemplo clásico de este problema es el acceso a base de datos,, imaginemos que tenemos una aplicación con el siguiente esquema:

Tenemos tres módulos, uno que se encarga de la interfaz de usuario, otro módulo con la lógica de negocio y un tercer módulo de más bajo nivel que se encarga de acceder a la base de datos. Las flechas indican las dependencias entre los distintos módulos. 

Según está el esquema actualmente si necesitamos modificar la implementación en el módulo de acceso a base de datos, afectaría directamente a la implementación de la lógica de negocio.

Si aplicamos la inversión de dependencias añadiendo una interfaz entre ambos módulos el esquema quedaría de la siguiente manera.

Con esta arquitectura sería mucho más sencillo modificar la implementación de la base de datos, por ejemplo si en un futuro la tecnología de la base de datos cambiase, crearemos una nueva implementación pero utilizando la misma interfaz.

Veamos otro ejemplo con código. Supongamos que tenemos un software que procesa una serie de documentos almacenados en una base de datos y les aplica una firma. El código estaría formado por tres clases, una que contiene el acceso a la base de datos, otra con los algoritmos necesarios para generar la firma y una tercera clase con la lógica de negocio que implementa y utiliza instancias de las anteriores clases.

public class MongoDatabase {
    public Document getDocument(int id){
        // Access to DB and returns the document with the corresponding ID
    }
}
 
public class MySignature {
    public void signMD5withRSA(Document doc){
        // Algorithm to sign document
    }
}
 
public class ProcessDocument {
    public void signDocument(int id) {
         
        MongoDatabase mongoDatabase = new MongoDatabase();
        Document doc = mongoDatabase.getDocument(id);
 
        MySignature signature = new MySignature();
        signature.signMD5withRSA(doc);
    }
}

En este caso la clase de más alto nivel, que procesa los documentos, está dependiendo de módulos de bajo nivel, como son el acceso a base de datos y el proceso de firma de documentos. Si en un futuro nuestro requerimientos del software cambian y nos vemos obligados a modificar el algoritmo de firma o utilizar otro cliente de base de datos, es muy probable que las modificaciones afecten a la clase ProcessDocument.

Si aplicamos el principio de inversión de dependencias, deberíamos sustituir en nuestra clase ProcessDocument las dependencias a implementaciones concretas por abstracciones. Para ello crearemos dos interfaces que definen los comportamiento que debe tener una clase para acceder a la base de datos o para realizar una firma. Ahora la implementación concreta es recibida en el constructor de ProcessDocument. El código resultante sería el siguiente.

public interface IDatabase {
    Document getDocument(int id);
}
 
public class MongoDatabase implements IDatabase {
      
    @Override
    public Document getDocument(int id){
        // Get document from mongo DB
    }
}
 
public interface ISignature {
    void sign(Document doc);
}
 
public class MySignature implements ISignature {
      
    @Override
    public void sign(Document doc){
        // Signature logic
    }
}
 
public class ProcessDocument {
    private final IDatabase database;
    private final ISignature signature;
 
    public ProcessDocument(IDatabase database, ISignature signature) {
        this.database = database;
        this.signature = signature;
    }
     
    public void signDocument(int id) {
        Document doc = this.database.getDocument(id);
        this.signature.sign(doc);
    }
}

Conclusión

Aplicando el principio de inversión de dependencias conseguimos que las clases de alto nivel no trabajen directamente con las clases de bajo nivel, para ello se utiliza interfaces que crean una capa abstracta intermedia entre ambos componentes, otorgando a la arquitectura de nuestra aplicación mayor flexibilidad y menor acoplamiento, características que a largo plazo pueden ser muy interesantes para la mantenibilidad de nuestro software.

El principio de inversión de dependencias es el último de esta serie de post sobre los principios SOLID, después de repasar todos ellos podemos comprobar que todos los principios están estrechamente relacionados entre sí y que la aplicación de algunos de ellos facilita la aplicación de los siguientes, ya que todos tienen como objetivo la realización de un código limpio, el resultado será un software con menor acoplamiento, más flexible, más estable, más fácil de mantener y de escalar.

¡Esperamos que estos ejemplos os hayan ayudado a entender los principios de SOLID! 

Compartir en:

Relacionados