Categorie
PHP

PHP – Basi sulla Dependency Injection

Probabilmente uno dei più grandi e “moderni” problemi delle programmazione OOP sono le dipendenze.
Se desideriamo scrivere codice di buona qualità, (quindi buone applicazioni) si dovrebbe limitare il più possibile gli effetti delle dipendenze tra le nostre classi.

Per fortuna esiste un pattern di progettazione che può aiutarci sotto questo punto di vista. Si chiama Dependency Injection (DI). Esso consente di iniettare gli oggetti in una classe, invece di crearli al suo interno.

In questo articolo, voglio spiegarti come mai gli sviluppatori PHP dovrebbero considerare di sfruttare la dependency injection, durante la creazione di grandi progetti scalabili.

Cosa sono le dipendenze

Quando si fa uso delle programmazione orienta agli oggetti (OOP), comprendere cosa sono le dipendenza è basilare. Inoltre visto che in questo articolo si parlerà della Dependency Injection, ritengo doveroso specificarlo per chi ancora non ha familiarità con questi concetti.

In informatica, per accoppiamento o dipendenza si intende il grado con cui ciascuna componente di un programma fa affidamento su ciascuna delle altre componenti, dipendendo, appunto, da esso.

Quando una classe A utilizza un’altra classe B, allora A dipende da B.
A non può svolgere il suo lavoro senza B, ed A non può essere riutilizzata senza anche riutilizzare B.
In tale situazione la classe A è chiamata “dipendente” e la classe B si chiama “dipendenza”.

Detto questo risulta piuttosto evidente perchè le dipendenze sono negative. Esse riducono il riutilizzo che invece ha un notevole impatto positivo sulla velocità di sviluppo, la qualità e la leggibilità del codice.
[box type=”note”]Le dipendenze contribuiscono a rendere poco flessibile il sistema.[/box]

Cos’è la Dependency Injection

Una volta compreso cosa sono le dipendenze, cerchiamo di definire meglio il concetto di iniezione di dipendenza.
Consideriamo questo semplice snippet di codice e vediamo quale poterebbe essere un approccio senza iniezione delle dipendenze:

class MyClass
{
    private $_dep;

    public function __construct($param1, $param2)
    {
        $this->_dep = new Dependency($param1, $param2);  
    }
}

$obj = new MyClass('param1', 'param2');

In questo modo, creare un’stanza di MyClass è semplice e soprattutto veloce, ma perdiamo moltissimo in termini di customizzazione.

Inoltre secondo quanto detto in precedenza, si evince subito che questo approccio, anche se a prima vista può sembrare corretto, porta a dei problemi. Infatti si crea palesemente la dipendenza nel costruttore. In questo modo la classe MyClass è fortemente accoppiata alla sua dipendenza, la classe Dependency.

Una modifica al costruttore di Dependency (l’aggiunta di un parametro per esempio), si ripercuoterà a cascata su qualsiasi punto del programma in cui ho creato istanze della classe MyClass.
Inoltre i parametri passati al costruttore di MyClass, non centrano nulla con il suo ambito (scope). Ogni classe dovrebbe essere responsabile di una sola cosa.

Consideriamo ora questa modifica:

class MyClass
{
    private $_dep;

    public function __construct()
    {
        $this->_dep = Registry::get('myDep');  
    }
}

$dep = new Dependency('param1', 'param2');  
Registry::set('myDep',$dep);
$obj = new MyClass();

Con questo nuovo approccio evitiamo di passare i parametri di Dependency a MyClass, utilizzando un registro globale.
Tuttavia ora MyClass dipende dal registro!!
Direi che non abbiamo fatto un grosso passo in avanti.

Ma se invece di creare/ottenere la dipendenza nel costruttore di MyClass gliela iniettassimo? Vediamo:

class MyClass
{
    private $_dep;

    public function __construct($dep)
    {
        $this->_dep = $dep;  
    }
}

$dep = new Dependency('param1', 'param2');  
$obj = new MyClass($dep);

Molto meglio direi.
Abbiamo appena applicato la forma più comune di Dependency Injection, passando (iniettando) le dipendenze direttamente al costruttore della classe.

[box type=”note”]L’iniezione di dipendenza è la tecnica per cui i componenti ottengono le loro dipendenze attraverso i costruttori, i metodi, o direttamente nelle proprietà.[/box] Anche se perdiamo qualcosina in semplicità di utilizzo, i vantaggi sono indubbi. Prima di tutto le configurazioni diventano naturali:

class MyClass
{
    private $_dep;

    public function __construct($dep)
    {
        $this->_dep = $dep;  
    }
}

$dep = new Dependency('new_param1', 'new_param2', 'param3');  
$obj = new MyClass($dep);

E se volessimo cambiare l’oggetto Dependency? Per esempio se volessimo rimpiazzarlo con un oggetto mock per il testing oppure utilizzare semplicemente una dipendenza diversa, aiutandoci eventualmente con un’interfaccia?
Niente di più facile… ora!

interface depInterface
{
    function doSomething(); 
}

class MyClass
{
    private $_dep;

    public function __construct(depInterface $dep)
    {
        $this->_dep = $dep;  
    }
}

$dep = new DependencyBis(); //Implementa depInterface
$obj = new MyClass($dep);

Ora, la configurazione dell’oggetto Dependency è semplice, ed anche sostituirlo con un’altra classe è molto facile. Il bello è che tutto è modificabile senza “toccare” l’istanza di MyClass.

Questo si traduce in tanto lavoro, tanti errori e tante preoccupazioni in meno quando ci troviamo a dover modificare i componenti dell’applicazione.

Tipi di iniezione

Ci sono più modi con cui le dipendenze possono essere iniettate. Ciascuno presenta vantaggi e svantaggi da considerare al momento della scelta.

Constructor-Injection

Si tratta sicuramente della modalità più utilizzata. L’idea di base è che tutti i collaboratori (le dipendenze) debbano essere iniettati nel costruttore, quindi dovranno essere forniti prima di poter creare un’istanza dell’oggetto.

$dep = new Dependency(); 

$obj = new MyClass($dep);

Pro

  1. Se la dipendenza è un requisito e la classe non può funzionare senza, iniettarla tramite il costruttore ci assicura che è presente quando l’oggetto la utilizzerà visto che l’oggetto non può essere istanziato senza di essa.
  2. Visto che il costruttore viene chiamato solo una volta quando l’oggetto viene creato, se lo desideriamo, siamo sicuri che la dipendenza non cambierà durante la vita dell’oggetto.
  3. In caso di molte dipendenze avremo soltanto un costruttore con molti argomenti.

Contro

  1. Non è possibile l’iniezione parziale delle dipendenze visto che dobbiamo passare tutti gli argomenti al costruttore in fase di creazione dell’oggetto.
  2. Da questo si evince che non è adatta per lavorare con le dipendenze opzionali.
  3. L’oggetto risulta immutabile e quindi non riconfigurare.

Setter-Injection

L’idea di base è che abbiamo un costruttore senza argomenti, e le dipendenze vengono passate attraverso i metodi setter (spesso si utilizza un prefisso init nella dichiarazione del metodo).

$dep = new Dependency(); 

$obj = new MyClass();
$obj->initDep($dep);

Pro

  1. Rende possibile l’iniezione parziale delle dipendenze.
  2. Funziona bene con le dipendenze opzionali. Se non avete bisogno della dipendenza, basta non chiamare il setter.
  3. Rende mutabile l’oggetto attraverso re-iniezione delle dipendenze, chiamando il setter più volte.

Contro

  1. In caso di molte dipendenze dovremmo scrivere altrettanti setter.
  2. Non si può essere sicuri che il setter sarà chiamato e quindi è necessario aggiungere dei controlli per assicurarci che le dipendenze richieste siamo iniettate.

Property Injection

Con questa modalità (chiamata anche Field Injection) le dipendenze vengono iniettate direttamente nelle proprietà pubbliche della classe.

$dep = new Dependency(); 

$obj = new MyClass();
$obj->_dep = $dep;

Si tratta di una soluzione poco utilizzata e per certi versi simile alla Setter Injection. Ha tutti gli aspetti negativi della Setter Injection, in più non essendo possibile utilizzare il type hinting non siamo neanche in grado di assicurarci che le dipendenze passate siano del tipo gusto.

A prima vista la Setter Injection potrebbe apparire la migliore o quantomeno preferibile visto che non vengono passati argomenti al costruttore, rendendo così più semplice la creazione di oggetti in ambiente di produzione o test. Inoltre molti la sostengono perché passare un gran numero di dipendenze (argomenti) al costruttore può risultare poco maneggevole, soprattutto quando alcune dipendenze sono opzionali.
Tuttavia a mio parere la Constructor Injection è la vincitrice, essa impone l’ordine di inizializzazione degli oggetti e previene da dipendenze circolari.
Con la Setter Injection non è chiaro in quale ordine le cose devono essere istanziate, e non garantisce che questo venga fatto. Ci troviamo così costretti a dover controllare che la dipendenza sia stata istanziata ogni volta che la dobbiamo usare, visto che non c’è niente che ci garantisca che questo sia stato fatto.
In altre parole la Constructor Injection forza l’ordine e la completezza delle istanziazioni.
L’aspetto negativo principale della Constructor Injection è che non permette la riconfigurazione dell’oggetto re-iniettando nuove dipendenze.
Se si vuole “riconfigurare” il dipendente, si dovrà crearne una nuova istanza utilizzando le nuove dipendenze e scartare l’altra.

Personalmente trovo la Constructor Injection più facile da usare ed il codice è molto più facile da leggere e capire.

[box type=”note”]Attraverso il type-hinting possiamo accorgerci immediatamente (sarà generato un errore) se viene iniettata una dipendenza non idonea.[/box]

In conclusione

In questo articolo ho cercato di spiegarti perchè questo pattern sia così tanto di moda e perchè dovresti cercare di utilizzarlo nelle tue applicazioni, soprattutto in progetti a lungo termine con costanti e continue evoluzioni future.

Sicuramente applicare la Dependency Injection ha dei costi, di tempo e pianificazione dell’applicazione ma ne saremo ripagati con gli interessi in futuro.

Se le classi fossero tra loro strettamente accoppiate, ecco che il progetto diventa estremamente complicato da sviluppare ed evolvere (e testare). Una semplice modifica ad una classe diventa un muro altissimo da valicare, perchè tutto risulta collegato insieme e questo porterà a modifiche a cascata su altre classi.

Tengo a precisare che, l’argomento è molto lontano dall’essere banale, e come tale merita una stretta e approfondita analisi che non può essere conclusa nelle poche righe di questo articolo. Non  a caso ho scelto di modificare il titolo originale: Capire la Dependency Injection in quello attuale.

Argomenti come Service Locator e soprattutto Dependency Injection Container (DiC), possono essere una logica continuazione ed approfondimento sull’argomento.

Risorse

http://en.wikipedia.org/wiki/Dependency_injection
http://www.martinfowler.com/articles/injection.html

Ottimo contributo di Anthony Ferrara sull’argomento.

2 risposte su “PHP – Basi sulla Dependency Injection”

Ciao, innanzi tutto complimenti per l’articolo, davvero chiaro ed intuitivo.
Forse la mia domanda ti sembrerà stupida, ma per scavalcare il limite della Costructor Injection di non permettere la riconfigurazione dell’oggetto re-iniettando nuove dipendenze, non è possibile accoppiarla alla Setter Injection? in altre parole la nostra classe non potrebbe prevedere entrambe? creerebbe confusione?

class MyClass{

private $_dep;

public __contruct($dep){
$this->_$dep = $dep;
}

public initDep($dep){
$this->$_dep = $dep;
}
}

Grazie in anticipo per la risposta e ancora complimenti! 🙂

Di solito quando le dipendenze non sono immutabili si tende a preferire la setter.
Tuttavia, almeno personalmente, non vedo niente di sbagliato nel tuo esempio che ti permetterebbe di unire “sicurezza” e “flessibilità”.
Magari potresti prendere in considerazione di eseguire l’iniezione da un unico punto (il setter) anche dal costruttore quindi:


public __contruct($dep){
$this->initDep($dep);
}

Grazie a te. Ciao

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.