Domande frequenti su DI (FAQ)

DI è un altro nome per IoC?

Inversion of Control (IoC) è un principio focalizzato sul modo in cui il codice viene eseguito – se il tuo codice esegue codice altrui o se il tuo codice è integrato in codice altrui, che poi lo chiama. IoC è un termine ampio che include eventi, il cosiddetto Principio di Hollywood e altri aspetti. Parte di questo concetto sono anche le factory, di cui parla la Regola n. 3: lascialo alla factory, e che rappresentano un'inversione per l'operatore new.

Dependency Injection (DI) si concentra sul modo in cui un oggetto viene a conoscenza di un altro oggetto, cioè delle sue dipendenze. È un design pattern che richiede il passaggio esplicito delle dipendenze tra oggetti.

Si può quindi dire che DI è una forma specifica di IoC. Tuttavia, non tutte le forme di IoC sono adatte dal punto di vista della pulizia del codice. Ad esempio, tra gli antipattern ci sono tecniche che lavorano con lo stato globale o il cosiddetto Service Locator.

Cos'è il Service Locator?

È un'alternativa alla Dependency Injection. Funziona creando un repository centrale dove sono registrati tutti i servizi o le dipendenze disponibili. Quando un oggetto ha bisogno di una dipendenza, la richiede al Service Locator.

Rispetto alla Dependency Injection, tuttavia, perde in trasparenza: le dipendenze non vengono passate direttamente agli oggetti e non sono quindi facilmente identificabili, il che richiede l'esame del codice per rivelare e comprendere tutti i legami. Anche il testing è più complesso, perché non possiamo semplicemente passare oggetti mock agli oggetti testati, ma dobbiamo passare attraverso il Service Locator. Inoltre, il Service Locator infrange il design del codice, poiché i singoli oggetti devono essere a conoscenza della sua esistenza, il che differisce dalla Dependency Injection, dove gli oggetti non sono consapevoli del container DI.

Quando è meglio non usare DI?

Non sono note difficoltà associate all'uso del design pattern Dependency Injection. Al contrario, ottenere dipendenze da luoghi globalmente accessibili porta a tutta una serie di complicazioni, così come l'uso del Service Locator. Pertanto, è consigliabile utilizzare sempre DI. Questo non è un approccio dogmatico, ma semplicemente non è stata trovata un'alternativa migliore.

Tuttavia, esistono alcune situazioni in cui non passiamo oggetti e li otteniamo dallo spazio globale. Ad esempio, durante il debugging del codice, quando è necessario stampare il valore di una variabile in un punto specifico del programma, misurare la durata di una certa parte del programma o registrare un messaggio. In tali casi, quando si tratta di operazioni temporanee che verranno successivamente rimosse dal codice, è legittimo utilizzare un dumper, un cronometro o un logger globalmente accessibili. Questi strumenti, infatti, non appartengono al design del codice.

L'uso di DI ha i suoi lati negativi?

L'uso della Dependency Injection comporta degli svantaggi, come ad esempio una maggiore complessità nella scrittura del codice o prestazioni peggiori? Cosa perdiamo quando iniziamo a scrivere codice in conformità con DI?

DI non ha alcun impatto sulle prestazioni o sui requisiti di memoria dell'applicazione. Le prestazioni del DI Container possono giocare un certo ruolo, tuttavia, nel caso di Nette DI, il container viene compilato in PHP puro, quindi il suo overhead durante l'esecuzione dell'applicazione è essenzialmente nullo.

Durante la scrittura del codice, è spesso necessario creare costruttori che accettano dipendenze. In passato questo poteva essere noioso, ma grazie agli IDE moderni e alla promozione delle proprietà del costruttore, ora è questione di pochi secondi. Le factory possono essere facilmente generate usando Nette DI e il plugin per PhpStorm con un clic del mouse. D'altra parte, scompare la necessità di scrivere singleton e punti di accesso statici.

Si può affermare che un'applicazione progettata correttamente che utilizza DI non è né più corta né più lunga di un'applicazione che utilizza singleton. Le parti di codice che lavorano con le dipendenze vengono semplicemente estratte dalle singole classi e spostate in nuovi luoghi, cioè nel container DI e nelle factory.

Come riscrivere un'applicazione legacy in DI?

La transizione da un'applicazione legacy alla Dependency Injection può essere un processo impegnativo, soprattutto per applicazioni grandi e complesse. È importante approcciare questo processo in modo sistematico.

  • Durante la transizione alla Dependency Injection, è importante che tutti i membri del team comprendano i principi e le procedure utilizzate.
  • Innanzitutto, esegui un'analisi dell'applicazione esistente e identifica i componenti chiave e le loro dipendenze. Crea un piano su quali parti verranno refattorizzate e in quale ordine.
  • Implementa un container DI o, ancora meglio, utilizza una libreria esistente, ad esempio Nette DI.
  • Refattorizza gradualmente le singole parti dell'applicazione per utilizzare la Dependency Injection. Ciò può includere la modifica di costruttori o metodi in modo che accettino le dipendenze come parametri.
  • Modifica i punti nel codice in cui vengono creati oggetti con dipendenze, in modo che invece le dipendenze vengano iniettate dal container. Ciò può includere l'uso di factory.

Ricorda che la transizione alla Dependency Injection è un investimento nella qualità del codice e nella manutenibilità a lungo termine dell'applicazione. Sebbene possa essere impegnativo apportare queste modifiche, il risultato dovrebbe essere un codice più pulito, modulare e facilmente testabile, pronto per future estensioni e manutenzione.

Perché si preferisce la composizione all'ereditarietà?

È preferibile utilizzare la composizione invece dell'ereditarietà, perché serve a riutilizzare il codice senza doversi preoccupare delle conseguenze delle modifiche. Fornisce quindi un legame più lasco, in cui non dobbiamo preoccuparci che la modifica di un codice causi la necessità di modificare un altro codice dipendente. Un esempio tipico è la situazione nota come constructor hell.

È possibile utilizzare Nette DI Container al di fuori di Nette?

Assolutamente. Nette DI Container fa parte di Nette, ma è progettato come una libreria autonoma che può essere utilizzata indipendentemente dalle altre parti del framework. Basta installarla tramite Composer, creare un file di configurazione con la definizione dei tuoi servizi e quindi, con poche righe di codice PHP, creare il container DI. E puoi iniziare subito a sfruttare i vantaggi della Dependency Injection nei tuoi progetti.

Come appare l'uso concreto, compresi i codici, è descritto nel capitolo Nette DI Container.

Perché la configurazione è nei file NEON?

NEON è un linguaggio di configurazione semplice e facilmente leggibile, sviluppato nell'ambito di Nette per impostare applicazioni, servizi e le loro dipendenze. Rispetto a JSON o YAML, offre opzioni molto più intuitive e flessibili per questo scopo. In NEON è possibile descrivere naturalmente legami che in Symfony & YAML non sarebbe possibile scrivere affatto, o solo tramite una descrizione complessa.

Il parsing dei file NEON non rallenta l'applicazione?

Sebbene i file NEON vengano parsati molto rapidamente, questo aspetto non ha alcuna importanza. Il motivo è che il parsing dei file avviene solo una volta al primo avvio dell'applicazione. Successivamente, viene generato il codice del container DI, salvato su disco ed eseguito ad ogni richiesta successiva, senza la necessità di eseguire ulteriori parsing.

Così funziona in ambiente di produzione. Durante lo sviluppo, i file NEON vengono parsati ogni volta che il loro contenuto viene modificato, in modo che lo sviluppatore abbia sempre un container DI aggiornato. Il parsing stesso è, come detto, questione di un attimo.

Come accedo ai parametri nel file di configurazione dalla mia classe?

Teniamo presente la Regola n. 1: fattelo passare. Se una classe richiede informazioni dal file di configurazione, non dobbiamo pensare a come ottenere quelle informazioni, ma semplicemente le chiediamo – ad esempio tramite il costruttore della classe. E il passaggio lo effettuiamo nel file di configurazione.

In questo esempio, %myParameter% è un segnaposto per il valore del parametro myParameter, che viene passato al costruttore della classe MyClass:

# config.neon
parameters:
	myParameter: Some value

services:
	- MyClass(%myParameter%)

Se vuoi passare più parametri o utilizzare l'autowiring, è consigliabile impacchettare i parametri in un oggetto.

Nette supporta PSR-11: Container interface?

Nette DI Container non supporta direttamente PSR-11. Tuttavia, se hai bisogno di interoperabilità tra Nette DI Container e librerie o framework che si aspettano PSR-11 Container Interface, puoi creare un semplice adapter, che fungerà da ponte tra Nette DI Container e PSR-11.

versione: 3.x