TIL - Principio di inversione della dipendenza


Oggi ho letto un paper sul principio di inversione della dipendenza chiamato anche DIP1.

DIP fa parte di una serie di principi che insieme formano il S.O.L.I.D.:

  • S -> SRP (Single Responsability Principle)
  • O -> OCP (Open Closed Principle)
  • L -> LSP (Liskov substitution Principle)
  • I -> ISP (Interface Segregation Principle)
  • D -> DIP (Dependency Inversion Principle)

La DIP si basa principalmente su due semplici regole:

  1. Un modulo di alto livello non dovrebbe dipendere da un modulo di basso livello. Entrambi dovrebbero dipendere da un’astrazione.

  2. Le astrazioni non dovrebbero dipendere dai dettagli. I dettagli dovrebbero dipendere dalle astrazioni.

Prendiamo in esempio una semplice struttura così composta:

+---------------------+         +----------------------+         +--------------------+
|                     |         |                      |         |                    |
|                     |         |                      |         |                    |
|    Policy Layer     +--------->   Mechanical Layer   +--------->  Utility Layer     |
|                     |         |                      |         |                    |
|                     |         |                      |         |                    |
+---------------------+         +----------------------+         +--------------------+

In questo esempio classi di policies di alto livello usa meccanismi di basso livello che a loro volta usano dettagliate classi di utilities.
In questo caso possiamo dire che la dipendenza è transitiva, il Policy Layer dipende anche dall’Utility Layer violando il principio di inversione della dipendenza.

Vediamo come creare un modello più appropriato:

                  +-------------------+
+------------+    |Mechanism Interface|
|Policy Layer+---->                   |
+------------+    |abstract           |
                  +--------^----------+
                           |
                           |             +-----------------+
                  +--------+-------+     |Utility Interface|
                  |Mechanism Layer |----->                 |
                  +----------------+     |abstract         |
                                         +--------^--------+
                                                  |
                                                  |
                                           +------+------+
                                           |Utility Layer|
                                           +-------------+

Ogni layer di basso livello è rappresentato da una classe astratta così da astrarre la dipendenza delle classi di alto livello da quelle di basso livello, in questo modo non solo rompiamo la dipendenza transitiva fra Policy e Utility ma anche fra Mechanism e Utility, in questo modo ogni cambiamento del Mechanism Layer o dell’Uitlity Layer è completamente trasparente al Policy Layer e a sua volta il Policy Layer potrà essere riusato ogni qual volta si ripresenta l’occasione di doversi interfacciare a layer di basso livello conformi al Mechanism Layer; invertendo la dipendenza abbiamo creato una struttura molto più flessibile, durabile e mobile; fantastico. no?

Ma in pratica?

Un esempio più pratico potrebbe essere quello di una classe User che usa una classe MySqlDriver per compiere operazioni di scrittura sul db:

public class User {
	
	[...]

	public void save() {
		MySqlDriver.writeOnTheDb('user_table', ...);
	}
}

In questo esempio vediamo una dipendenza diretta fra la classe User e la classe MySqlDriver, vediamo un primo step per poter rimediare, astraendo in classi più generiche:

public interface DatabaseDriver {
    void saveOnTheDb(String table, String command);
}

Abbiamo creato una interfaccia generica specificando il metodo saveOnTheDb.

public class OracleDriver implements DatabaseDriver {
    @Override
    public void saveOnTheDb(String table, String command) {
        System.out.println("FROM ORACLE: table = [" + table + "], command = [" + command + "]");
    }
}
public class MySqlDriver implements DatabaseDriver {
    @Override
    public void saveOnTheDb(String table, String command) {
        System.out.println("FROM MYSQL: table = [" + table + "], command = [" + command + "]");
    }
}

Abbiamo implementato la classe DatabaseDriver in due classi differenti, uno per un db Orable e uno per un db MySql

public class DbAction {
    DatabaseDriver driver;

    void setDriver(DatabaseDriver d) {
        this.driver = d;
    }

    void saveOnTheDb(String table, String command) {
        driver.saveOnTheDb(table, command);
    }
}

Abbiamo creato una classe DbAction che ci permettera di astrarre User dalla scelta del tipo di db da usare, questa classe come potete notare ignora completamente il tipo di db che sta usando.

public class User {
    public void save(DbAction driver) {
        driver.saveOnTheDb("user_table", "...");
    }
}

Ed ecco qui la nostra classe User che è TOTALMENTE distaccata dalla dipendenza di basso livello.

public class main {
    public static void main(String args[]) {
        User u = new User();
        DbAction d = new DbAction();
   	// I want to use a MySql DB
	MySqlDriver mysql = new MySqlDriver();
	d.setDriver(mysql);
	u.save(d);
		
	//Now I want to use an Oracle DB
	OracleDriver oracle = new OracleDriver();
	d.setDriver(oracle);
	u.save(d);
    }
}

Grazie all’astrazione dalla classe di basso livello noi siamo in grado di poter usare entrambi i DB senza doverci preoccupare di modificare la classe User che per l’appunto è una classe di alto livello. Riuscite a percepire il potere che ne deriva? Infatti il Dipendency Inversion Principle è alla base di ogni framework fatto bene.


  1. DIP