2010-08-19 15 views
16

In primo luogo, voglio limitare questa domanda solo allo sviluppo web. Quindi questo è indipendente dal linguaggio finché il linguaggio viene utilizzato per lo sviluppo web. Personalmente, sto arrivando a questo da uno sfondo in PHP.Quale è meglio: Iniezione delle dipendenze + Registro o Iniezione delle dipendenze o Registro globale?

Spesso è necessario utilizzare un oggetto da più ambiti. Ad esempio, potrebbe essere necessario utilizzare una classe di database nell'ambito normale ma anche da una classe di controller. Se creiamo l'oggetto del database in ambito normale, non possiamo accedervi dall'interno della classe controller. Desideriamo evitare di creare due oggetti di database in ambiti diversi e quindi abbiamo bisogno di un modo per riutilizzare la classe del database indipendentemente dall'ambito. Per fare ciò, abbiamo due opzioni:

  1. Rendere globale l'oggetto del database in modo che sia accessibile da qualsiasi luogo.
  2. Passa la classe del database alla classe controller sotto forma, ad esempio, di un parametro nel costruttore del controllore. Questo è noto come iniezione di dipendenza (DI).

Il problema diventa più complesso quando ci sono molte classi coinvolte tutti gli oggetti esigenti in molti ambiti diversi. In entrambe le soluzioni, questo diventa problematico perché se rendiamo globale uno dei nostri oggetti, stiamo mettendo troppo rumore nello scope globale e se passiamo troppi parametri in una classe, la classe diventa molto più difficile da gestire.

Pertanto, in entrambi i casi, si vede spesso l'uso di un registro. Nel caso globale, abbiamo un oggetto di registro che è reso globale e quindi aggiunge tutti i nostri oggetti e variabili a quelli che li rendono disponibili in qualsiasi oggetto, ma inserendo una sola variabile, il registro, nello scope globale. Nel caso DI, passiamo l'oggetto del Registro di sistema in ogni classe riducendo il numero di parametri a 1.

Personalmente, utilizzo quest'ultimo approccio a causa dei numerosi articoli che lo sostengono sull'utilizzo di globals ma ho riscontrato due problemi. In primo luogo, la classe di registro conterrà enormi quantità di ricorsione. Ad esempio, la classe del Registro di sistema conterrà le variabili di accesso al database richieste dalla classe del database. Pertanto, è necessario inserire la classe di registro nel database. Tuttavia, il database sarà necessario per molte altre classi e quindi il database dovrà essere aggiunto al registro, creato un ciclo. Le lingue moderne possono gestire questo problema o questo sta causando enormi problemi di prestazioni? Si noti che il registro globale non ne risente poiché non viene passato a nulla.

In secondo luogo, inizierò a passare grandi quantità di dati a oggetti che non ne hanno bisogno. Al mio database non interessa il mio router ma il router verrà passato al database insieme ai dettagli della connessione al database. Ciò è aggravato dal problema della ricorsione perché se il router ha il registro, il registro ha il database e il registro e il registro viene passato al database, quindi il database viene passato a se stesso tramite il router (cioè posso fare $this->registry->router->registry->database dall'interno della classe del database `).

Inoltre, non vedo ciò che il DI mi sta dando se non più complessità. Devo passare una variabile extra in ogni oggetto e devo usare gli oggetti di registro con $this->registry->object->method() invece di $registry->object->method(). Questo ovviamente non è un grosso problema, ma sembra inutile se non mi dà nulla rispetto all'approccio globale.

Ovviamente, questi problemi non esistono quando utilizzo DI senza un registro ma poi devo passare ogni oggetto "manualmente", ottenendo costruttori di classe con un numero ridicolo di parametri.

Dato questi problemi con entrambe le versioni di DI, non è un registro globale superiore?Cosa sto perdendo usando un registro globale su DI?

Una cosa che viene spesso menzionata quando si parla di DI contro Globali è che i globali inibiscono la capacità di testare correttamente il programma. In che modo esattamente i globals mi impediscono di provare un programma in cui DI non lo farebbe? Ho letto in molti posti che ciò è dovuto al fatto che un globale può essere modificato da qualsiasi luogo e quindi è difficile deridere. Tuttavia, mi sembra che, almeno in PHP, gli oggetti siano passati per riferimento, cambiando un oggetto iniettato in qualche classe lo cambierai anche in qualsiasi altra classe in cui è stato iniettato.

+0

Bene, un piccolo punto. In PHP 5 (penso che 5.2, ma potrebbe essere stato precedente) tutti gli oggetti vengono passati per riferimento quando vengono passati a metodi/funzioni. Quindi non stai effettivamente spostando un sacco di dati, stai solo incrementando il conteggio dei riferimenti e spostando ciò che si riduce a un puntatore ... (A cui ti accengi più avanti nel post) ... – ircmaxell

+0

Questo significa che non ci dovrebbero essere differenze in termini di prestazioni tra il passaggio di un oggetto senza proprietà e un oggetto con 1000 proprietà (tranne ovviamente per la differenza originale quando vengono inizializzate)? La ricorsione lo altera in qualche modo? –

+0

La ricorsione è un problema, non è possibile, ad esempio, distruggere un oggetto quando lo si desidera, a meno che tutti i riferimenti all'oggetto non vengano distrutti. L'inserimento di debug_backtrace() in qualche __destructor spesso rende lo script interrotto, questo solo a causa delle ricorsioni. È bene che tu menzioni la ricorsione, dovresti prenderla in seria considerazione. Penso che l'iniezione renda la ricorsione ancora peggiore. Per quanto riguarda la connessione al database, una funzione globale get_db_conn() potrebbe fare il lavoro bene, tutto ciò che serve è mettere un $ statico database_conn = null; all'interno della funzione get_db_conn(). – Melsi

risposta

13

Affrontiamo questo uno per uno.

In primo luogo, la classe registro conterrà enormi quantità di ricorsione

Non dovete iniettare la classe Registry nella classe di database. Puoi anche fare il have dedicated methods on the Registry to create the required classes per te. Oppure, se si inserisce il Registro, è possibile semplicemente non memorizzarlo ma solo prelevare da esso ciò che è necessario affinché la classe sia istanziata correttamente. Nessuna ricorsione.

Si noti che il registro globale non ne risente poiché non viene passato a nulla.

Potrebbe non esserci ricorsione per il Registro stesso, ma gli oggetti nel Registro potrebbero avere riferimenti circolari. Ciò potrebbe portare a perdite di memoria quando si eliminano oggetti dal Registro di sistema con versioni di PHP precedenti alla 5.3 quando lo Garbage Collector non li raccoglie correttamente.

In secondo luogo, inizierò a passare grandi quantità di dati a oggetti che non ne hanno bisogno. Al mio database non interessa il mio router ma il router verrà passato al database insieme ai dettagli della connessione al database.

Vero. Ma è a questo che serve il Registro. Non è molto diverso dal passare $ _GLOBALS ai tuoi oggetti. Se non lo si desidera, non utilizzare un Registro di sistema, ma solo passare gli argomenti richiesti affinché le istanze di classe siano in uno stato valido. O semplicemente non salvarlo.

ho potuto fare $ this-> registry-> router-> registry-> banca dati

E 's improbabile che router espone un metodo pubblico per ottenere il Registro di sistema. Non sarete in grado di arrivare a database da $this attraverso router, ma sarete in grado di arrivare a database direttamente. Certamente. È un registro. Questo è quello per cui l'hai scritto. Se si desidera archiviare il registro nei propri oggetti, è possibile racchiuderli in un'interfaccia segregata che consente solo l'accesso a un sottoinsieme dei dati contenuti all'interno.

Ovviamente, questi problemi non esistono quando utilizzo DI senza un registro ma devo passare ogni oggetto "manualmente", ottenendo costruttori di classe con un numero ridicolo di parametri.

Non necessariamente. Quando si utilizza l'iniezione del costruttore, è possibile limitare il numero di argomenti a quelli assolutamente necessari per mettere l'oggetto in uno stato valido. Le restanti dipendenze opzionali possono essere impostate anche tramite l'iniezione setter. Inoltre, nessuno ti impedisce di aggiungere gli argomenti in un oggetto Array o Config. O utilizzare Builders.

Considerati questi problemi con entrambe le versioni di DI, non è un registro globale superiore? Cosa sto perdendo usando un registro globale su DI?

Quando si utilizza un Registro globale, si associa strettamente questa dipendenza alla classe. Ciò significa che le classi di utilizzo non possono più essere utilizzate senza questa concreta classe del Registro di sistema. Presumi che ci sarà solo questo Registro e non una diversa implementazione. Quando si iniettano le dipendenze, si è liberi di iniettare ciò che soddisfa la responsabilità della dipendenza.

Una cosa che viene spesso menzionata quando si parla di DI contro Globali è che i globali inibiscono la capacità di testare correttamente il programma. In che modo esattamente i globals mi impediscono di provare un programma in cui DI non lo farebbe?

Non ti impediscono di testare il codice. They just make it harder. Durante il test unitario si desidera avere il sistema in uno stato noto e riproducibile. Se il tuo codice ha dependencies on the global state, devi creare questo stato per ogni prova di funzionamento.

Ho letto in molti luoghi che ciò è dovuto al fatto che a livello globale può essere modificato da qualsiasi luogo, e quindi è difficile da deridere

corretta, se un test cambia lo stato globale, potrebbe influire sui test successivi se non lo si modifica. Ciò significa che devi impegnarti a ricreare l'ambiente oltre a impostare il soggetto sottoposto a test in uno stato noto. Questo potrebbe essere facile se c'è solo una dipendenza, ma cosa succede se ce ne sono molti e anche quelli dipendono dallo stato globale. Finirai nel Dependency Hell.

5

Inserirò questo come risposta in quanto vorrei includere il codice.

Ho eseguito il benchmark passando un oggetto contro usando global. Fondamentalmente ho creato un oggetto relativamente semplice, ma uno con un riferimento personale e un oggetto nidificato.

I risultati:

Passed Completed in 0.19198203086853 Seconds 
Globaled Completed in 0.20970106124878 Seconds 

E i risultati sono identici se rimuovere l'oggetto nidificato e il riferimento di sé ...

Quindi sì, sembra che non c'è alcuna vera differenza di prestazioni tra i due diversi metodi di trasmissione dei dati. Quindi, fare la scelta architettonica migliore (IMHO questo è il Dependency Injection) ...

Lo script:

$its = 10000; 
$bar = new stdclass(); 
$bar->foo = 'bar'; 
$bar->bar = $bar; 
$bar->baz = new StdClass(); 
$bar->baz->ar = 'bart'; 

$s = microtime(true); 
for ($i=0;$i<$its;$i++) passed($bar); 
$e = microtime(true); 
echo "Passed Completed in ".($e - $s) ." Seconds\n"; 

$s = microtime(true); 
for ($i=0;$i<$its;$i++) globaled(); 
$e = microtime(true); 
echo "Globaled Completed in ".($e - $s) ." Seconds\n"; 

function passed($bar) { 
    is_object($bar); 
} 

function globaled() { 
    global $bar; 
    is_object($bar); 
} 

provata su 5.3.2

Problemi correlati