Dependență de injectare Huh?

Șansele sunt, într-un anumit punct al învățării, că ați întâlnit termenul "injecție de dependență". Dacă sunteți încă relativ devreme în învățarea voastră, ați format probabil o expresie confuză și ați omis peste acea parte. Totuși, acesta este un aspect important al scrierii unui cod sustenabil (și verificabil). În acest articol, o voi explica într-un mod atât de simplu în care sunt capabil.


Un exemplu

Hai să intrăm într-o chestie destul de generică și să discutăm despre scurtcircuitul ei.

 clasa Foto / ** * @var DOP conexiunea la baza de date * / protejat $ db; / ** * Construct. * / funcția publică __construct () $ this-> db = DB :: getInstance (); 

La prima vedere, acest cod ar putea părea destul de inofensiv. Dar luați în considerare faptul că deja am codificat o dependență: conexiunea la baza de date. Ce se întâmplă dacă vrem să introducem un strat diferit de persistență? Sau, gândiți-vă în acest fel: de ce ar trebui să Fotografie obiect să comunicați cu surse externe? Nu încalcă conceptul de separare a preocupărilor? Cu siguranta. Acest obiect nu ar trebui să fie preocupat de nimic care nu este direct legat de a Fotografie.

Ideea de bază este că clasele tale ar trebui să fie responsabile pentru un singur lucru. În acest sens, nu ar trebui să fie responsabil pentru conectarea la baze de date și alte lucruri de acest gen.

Gândiți-vă la obiecte în același mod în care vă gândiți la animalul dvs. de companie. Câinele nu decide când se duce afară pentru o plimbare sau pentru a se juca în parc. Tu faci! Nu este locul lui de a lua aceste decizii.

Să recâștigăm controlul clasei și, în schimb, să trecem la conexiunea bazei de date. Există două modalități de a realiza acest lucru: constructor și injector setter, respectiv. Iată exemple de ambele:

Constructor Injecție

 clasa Foto / ** * @var DOP conexiunea la baza de date * / protejat $ db; / ** * Construct. * @param PDO $ db_conn Conexiunea bazei de date * / funcția publică __construct ($ dbConn) $ this-> db = $ dbConn;  $ photo = fotografie nouă ($ dbConn);

Mai sus, suntem injectarea toate dependentele necesare, atunci când rulează metoda constructorului clasei, mai degrabă decât să le codificați direct în clasă.

Setter Injection

 clasa Foto / ** * @var DOP conexiunea la baza de date * / protejat $ db; funcția publică __construct ()  / ** * Setează conexiunea bazei de date * @param PDO $ dbConn Conexiunea la baza de date. * / funcția publică setDB ($ dbConn) $ this-> db = $ dbConn;  $ photo = fotografie nouă; $ Foto-> setDB ($ dbConn);

Cu această schimbare simplă, clasa nu mai depinde de nicio conexiune specifică. Sistemul exterior își păstrează controlul complet, după cum este cazul. Deși este posibil să nu fie imediat vizibilă, această tehnică face de asemenea clasa mult mai ușor de testat, deoarece acum putem să frămânțăm baza de date când sunăm setDB metodă.

Chiar mai bine, dacă mai târziu vom decide să folosim o altă formă de persistență, datorită injecției de dependență, este un cinch.

"Injectarea dependenței este locul unde componentele își dau dependența prin constructori, metode sau direct în câmpuri".


The Rub

Există o problemă în ceea ce privește utilizarea injectorului de tip setter: face ca clasa să fie mult mai dificil de utilizat. Utilizatorul trebuie să fie pe deplin conștient de dependențele clasei și trebuie să-și amintească să le stabilească, în consecință. Luați în considerare, în jos, când clasa noastră ficțională necesită mai multe dependențe. Ei bine, urmând regulile modelului de injecție a dependenței, trebuie să facem:

 $ photo = fotografie nouă; $ Foto-> setDB ($ dbConn); $ Foto-> setConfig ($ config); $ Foto-> setResponse ($ răspuns);

Hopa; clasa poate fi mai modulară, dar am și pus pe confuzie și complexitate. Înainte, utilizatorul ar putea să creeze pur și simplu o nouă instanță Fotografie, dar, acum, trebuie să-și amintească să stabilească toate aceste dependențe. Ce enervant!


Soluția

Soluția la această dilemă este de a crea o clasă de containere care să gestioneze greutatea muncii pentru noi. Dacă ați întâlnit vreodată acest termen, "Inversion of Control (IoC)", acum știi la ce se referă.

Definiție: În ingineria software-ului, Inversion of Control (IOC) este o practică de programare orientată obiect unde cuplarea obiectului este legată la timpul de execuție de un obiect de asamblare și de obicei nu este cunoscută la momentul compilării folosind analiza statică.

Această clasă va stoca un registru al tuturor dependențelor pentru proiectul nostru. Fiecare cheie va avea o funcție asociată lambda care instantează clasa asociată.

Există câteva căi de abordat. Am putea fi explicite și să stocăm metode, cum ar fi poză nouă:

 // De asemenea, frecvent numit "Container" clasa IoC / ** * @var PDO Conectarea la baza de date * / protejat $ db; / ** * Creați o nouă instanță a Photo și setați dependențele. * / statică publică newPhoto () $ photo = new Photo; $ Foto-> setDB (static :: $ db); // $ photo-> setConfig (); // $ photo-> setResponse (); returnați $ photo;  $ photo = IoC :: newPhoto ();

Acum, $ fotografie va fi egal cu o nouă instanță a Fotografie clasă, cu toate setările necesare. În acest fel, utilizatorul nu trebuie să-și amintească să stabilească aceste dependențe manual; pur și simplu îl cheamă poză nouă metodă.

A doua opțiune, mai degrabă decât crearea unei metode noi pentru fiecare instanță de clasă, este de a scrie un container de registru generic, după cum urmează:

 clasa IoC / ** * @var PDO Conectarea la baza de date * / static protejat $ registry = array (); / ** * Adăugați un nou resolver la matricea de registru. * @param string $ nume Id * @ param obiect $ resolve Închidere care creează instanță * @return void * / registru funcțional static public ($ name, Closure $ resolve) static :: $ registry [$ name] = $ resolve;  / ** * Crearea instanței * @param string $ name ID * @return mixt * / funcția statică publică rezolvă ($ name) if (static :: registered ($ name)) $ name = static :: $ registru [$ name]; returnați $ name ();  aruncați o nouă Excepție ("Nimic înregistrat cu acest nume, nebun");  / ** * Determina daca id-ul este inregistrat * @param string $ name Id * @return bool Fie ca exista id sau nu * / functionala statica publica inregistrata ($ name) return array_key_exists ($ name, static :: $ registru); 

Nu lăsați acest cod să vă sperie; este foarte simplu. Când utilizatorul a sunat IoC :: registru metoda, ei adaugă un id, cum ar fi fotografie, și rezolvatorul său asociat, care este doar o lambda care creează instanța și stabilește toate dependențele necesare din clasă.

Acum, putem înregistra și rezolva dependențele prin intermediul IoC clasa, după cum urmează:

 // Adăugați 'photo' în matricea registrului, împreună cu un resolver IoC :: registru ('photo', function () $ photo = new Photo; $ photo-> setDB ('...'); ('...'); returnați $ photo;); // Returnați un nou exemplu de fotografie cu setări de dependență $ photo = IoC :: resolve ('photo');

Deci, putem observa că, cu acest model, nu instanțiăm manual clasa. În schimb, îl înregistrăm la IoC container și apoi solicitați o instanță specifică. Acest lucru reduce complexitatea pe care am introdus-o când am dezbrăcat dependențele de pe hardcod din clasă.

 // Înainte de $ photo = new Photo; // După $ photo = IoC :: rezolva ('foto');

Aproape același număr de caractere, dar, acum, clasa este mult mai flexibilă și mai testabilă. În utilizarea în lumea reală, probabil că doriți să extindeți această clasă pentru a permite crearea de singleturi, de asemenea.


Folosind metode magice

Dacă vrem să reducem lungimea IoC chiar și mai mult, putem profita de metodele magice - și anume __a stabilit() și __obține(), care va fi declanșată dacă utilizatorul apelează o metodă care nu există în clasă.

 clasa IoC protejat $ registry = array (); funcția publică __set ($ name, $ resolver) $ this-> registry [$ name] = $ resolver;  funcția publică __get ($ name) return $ this-> registry [$ name] (); 

Popularizat de Fabien Potencier, aceasta este o implementare extrem de minimă - dar va funcționa. Fie că este sau nu __obține() sau a stabilit() runurile vor depinde de faptul dacă utilizatorul stabilește o valoare sau nu.

Utilizarea de bază ar fi:

 $ c = noul IoC; $ c-> mailer = funcție () $ m = nou Mailer; // creați o nouă instanță a mailer // set creds, etc return $ m; ; // Fetch, băiat $ mailer = $ c-> mailer; // instanță mailer

Vă mulțumim pentru lectură!

Cod