Il Principio
Siamo arrivati al gran finale: è il momento di capire cosa diamine è quella “D” in S.O.L.I.D.
Ti accontento subito: “D” sta per Dependency Inversion (da ora DI), e si enuncia generalmente così:
High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions.
Tradotto nella lingua di Dante, possiamo scrivere:
I moduli di livello più alto non devono dipendere da quelli di livello più basso. Entrambi dovrebbero dipendere da astrazioni. Le astrazioni non dovrebbero mai dipendere dai dettagli. I dettagli dovrebbero dipendere da astrazioni.
Ora, messa così sembra una cosa piuttosto astrusa simile ad un sogno in Inception, ma non è così. In realtà, le cose sono ben diverse.
La Spiegazione
Al giorno d’oggi troverai molti software che già applicano questo principio in modo estensivo. Nonostante la definizione formale possa sembrare lunga e astrusa, infatti, tutto quello che suggerisce il principio è piuttosto semplice: usa le interfacce per far comunicare i tuoi oggetti, ed evita ogni contatto diretto tra classi concrete.
Se riprendi la definizione in inglese, la parola magica infatti è depend.
La frase più ripetuta è should depend on abstractions.
Le interfacce, d’altronde, servono proprio a questo: astrarre il più possibile. Quando hai un sistema che lavora su più livelli, e non sai quale sarà il loro destino, la cosa peggiore che puoi fare è far dipendere questi livelli l’uno dall’altro senza “proteggere” questa dipendenza con un’interfaccia e relativa astrazione.
A questo punto, però, ti starai chiedendo…
A Chicco, che cavolo vuol dire far comunicare degli oggetti tramite interfacce? Spiegati meglio!
Ti accontento subito.
L’Esempio
Immaginiamo di dover sviluppare un software che si occupi di effettuare il logging di informazioni. Nulla di troppo complesso: questo package dovrà implementare le seguenti feature:
- loggare un’informazione come ERROR;
- loggare un’informazione come WARNING;
- loggare un’informazione come INFO;
Inizialmente, questo sistema lavorerà con dei file di testo, ma l’azienda per cui lavori sta già prendendo accordi per supportare svariati tipi di database come sistema di storage, nel caso di sistemi più grandi e complessi.
Ora, prima dell’avvento di questo principio, si sarebbe fatto qualcosa del genere:
textFileLogWriter = $textFileLogWriter;
}
public function error($text)
{
$this->textFileLogWriter->write(‘ERROR: ‘ . $text);
}
public function warning()
{
$this->textFileLogWriter->write(‘Warning: ‘ . $text);
}
public function info()
{
$this->textFileLogWriter->write(‘Info: ‘ . $text);
}
}
class TextFileLogWriter {
public function write($text)
{
// scrivo il testo che mi viene passato in un file di log
// sul filesystem
}
}
Può sembrare tutto ok, all’apparenza. Si, il software funziona senza problemi, ma stiamo facendo un errore madornale. Un’entità di alto livello (la classe Logger), infatti, dipende da una di livello più basso (la classe TextFileLogWriter) che si occupa di lavorare con i file di testo.
Niente di più sbagliato.
Il principio della DI parla chiaro: tutto deve dipendere da un’astrazione.
La soluzione andrebbe quindi riformulata diversamente. La prima cosa da fare è definire un’interfaccia che rappresenti un qualsiasi sistema sottostante e non uno nello specifico.
logWriter = $logWriter;
}
public function error($text)
{
$this->logWriter->write(‘ERROR: ‘ . $text);
}
public function warning()
{
$this->logWriter->write(‘Warning: ‘ . $text);
}
public function info()
{
$this->logWriter->write(‘Info: ‘ . $text);
}
}
Et voilà! Abbiamo “ripulito” la classe da qualsiasi concretizzazione. Ora sa che può dipendere da qualsiasi classe che implementa la LogWriterInterface.
Lo step finale è definire l’implementazione dell’interfaccia da parte di TextFileLogWriter:
error(‘Azione non permessa dell’utente X’);
Visto? Pulizia estrema, disaccoppiamento dei componenti e codice facilmente manutenbile.
Ok, Fra… ma non capisco ancora quali sono i benefici di un’interfaccia a fare da “livello in più”.
Si, immagino possa risultare un po’ difficile… lascia allora che ti faccia un altro esempio. Il tuo logger fa faville ma, come detto in precedenza, c’è necessità di integrarlo anche con un sistema di database.
Con una struttura del genere, la modifica è praticamente immediata. Tutto quello che devi fare, infatti, è creare il tuo nuovo DatabaseLogWriter: