Chiar dacă injecția de dependență este un subiect rar predat începătorilor, este un model de design care merită mai multă atenție. Mulți dezvoltatori evită injectarea de dependență, deoarece nu știu ce înseamnă sau pentru că ei cred că nu au nevoie de ea.
În acest articol, voi încerca să vă conving de valoarea injecției de dependență. Pentru a face acest lucru, vă voi prezenta injecția de dependență, arătându-vă cât de simplu este în cea mai simplă formă.
S-au scris multe despre injectarea de dependență și există o grămadă de unelte și biblioteci care vizează simplificarea injectării dependenței. Există totuși un citat care surprinde confuzia pe care mulți oameni o au în legătură cu injecția de dependență.
"Dependența de injecție" este un termen de 25 de dolari pentru un concept de 5 cenți. - James Shore
Odată ce înțelegeți ideea care stă la baza injecției de dependență, veți înțelege și citatul de mai sus. Să începem cu un exemplu pentru a ilustra conceptul.
O aplicație iOS are multe dependențe, iar aplicația dvs. se poate baza pe dependențe pe care nici măcar nu le cunoașteți, adică nu le considerați dependente. Următorul fragment de cod arată implementarea unui a UIViewController
subclasa numit ViewController
. Implementarea include o metodă numită saveList:
. Puteți observa dependența?
#import "ViewController.h" @interface ViewController () @end @implementation ViewController - (void) saveList: (NSArray *) list if (lista esteKindOfClass: [NSArray class]]) NSUserDefaults * userDefaults = [NSUserDefaults standardUserDefaults] ; [userDefaults setObject: list forKey: @ "list"]; @Sfârșit
Cele mai des întâlnite dependențe sunt cele pe care ne bazăm pe cele mai multe. În saveList:
, stocăm o matrice în baza de date implicită a utilizatorului, accesibilă prin NSUserDefaults
clasă. Accesăm obiectul implicit implicit invocând standardUserDefaults
metodă. Dacă sunteți oarecum familiarizat cu dezvoltarea iOS sau OS X, atunci probabil că veți fi familiarizați cu NSUserDefaults
clasă.
Stocarea datelor în baza de date implicită a utilizatorilor este rapidă, ușoară și fiabilă. Mulțumită standardUserDefaults
, avem acces la baza de date implicită pentru utilizatori de oriunde din proiect. Metoda returnează un singur ton pe care îl putem folosi oricând și oriunde vrem. Viața poate fi frumoasă.
Singleton? Ori de câte ori și oriunde? Simți ceva? Nu numai că simt o dependență, ci și mirosul unei practici rele. În acest articol, nu vreau să deschid o cutie de viermi, discutând despre utilizarea și utilizarea greșită a singletonilor, dar este important să înțelegem că singletonii ar trebui să fie utilizați cu meticulozitate.
Cei mai mulți dintre noi au devenit atât de obișnuiți cu baza de date implicită a utilizatorilor, încât nu o considerăm o dependență. Dar cu siguranță este una. Același lucru este valabil și pentru centrul de notificare, pe care accesăm în mod obișnuit prin intermediul serviciului singleton accesibil prin defaultCenter
metodă. Uitați-vă la următorul exemplu de clarificare.
#import "ViewController.h" @interface ViewController () @end @implementation ViewController #pragma - marca #pragma Inițializare - (instanțătip) init auto = [super init]; dacă (auto) NSNotificationCenter * nc = [NSNotificationCenter defaultCenter]; [nc addObserver: selector auto: @selector (applicationWillEnterForeground :) nume: UIApplicationWillEnterForegroundNotification object: nil]; întoarce-te; #pragma marcă - #pragma marcă de gestionare a memoriei - (void) dealloc [[NSNotificationCenter defaultCenter] removeObserver: self]; #pragma marcă - #pragma marca Notificare manipulare - (void) applicationWillEnterForeground: (NSNotification *) notificare // ... @end
Scenariul de mai sus este foarte comun. Adăugăm controlorul de vizualizare ca observator pentru notificările cu numele UIApplicationWillEnterForegroundNotification
și eliminați-l ca observator în dealloc
metoda clasei. Aceasta adaugă o altă dependență față de ViewController
clasă, o dependență care este adesea ignorată sau ignorată.
Întrebarea pe care ați putea-o întrebați este: "Care este problema?" sau mai bine "Există o problemă?" Să începem cu prima întrebare.
Pe baza exemplelor de mai sus, se pare că nu există nicio problemă. Acest lucru nu este totuși complet adevărat. Controlerul de vizualizare depinde pe obiectul implicit partajat și centrul de notificare implicit pentru a-și face munca.
E o problemă? Aproape fiecare obiect se bazează pe alte obiecte pentru a-și face munca. Problema este că dependențele sunt implicit. Un dezvoltator nou pentru proiect nu știe că controlerul de vizualizare se bazează pe aceste dependențe prin inspectarea interfeței de clasă.
Testarea ViewController
clasa se va dovedi, de asemenea, a fi complicat, deoarece noi nu controla NSUserDefaults
și NSNotificationCenter
clase. Să analizăm câteva soluții la această problemă. Cu alte cuvinte, să vedem cum injecția de dependență ne poate ajuta să rezolvăm această problemă.
După cum am menționat în introducere, injecția de dependență este un concept foarte simplu. James Shore a scris un articol extraordinar despre simplitatea injectării dependenței. Există încă un citat de la James Shore despre ce injectare de dependență este în centrul său.
Dependența injecției înseamnă a da unui obiect variabilele de instanță. Într-adevăr. Asta e. - James Shore
Există o serie de modalități de a realiza acest lucru, dar este important să înțelegeți mai întâi ce înseamnă citatul de mai sus. Să vedem cum putem aplica acest lucru ViewController
clasă.
În loc să accesați centrul de notificare implicit din init
metodă prin defaultCenter
, vom crea o proprietate pentru centrul de notificare din ViewController
clasă. Aceasta este interfața actualizată a ViewController
clasa arată după această adăugare.
#import@interface ViewController: UIViewController @property (slab, nonatomic) NSNotificationCenter * defaultCenter; @Sfârșit
Acest lucru înseamnă, de asemenea, că trebuie să facem un pic de lucru suplimentar când inițializăm o instanță a ViewController
clasă. Așa cum scrie James, vom înmâna ViewController
exemplu variabilele de instanță. Așa este injecția simplă a dependenței. Este un nume fantezist pentru un concept simplu.
// Initialize ViewController ViewController * viewController = [[ViewController alloc] init]; // Configurați vizualizatorul [viewController setNotificationCenter: [NSNotificationCenter defaultCenter]];
Ca urmare a acestei schimbări, implementarea sistemului ViewController
schimbări de clasă. Aceasta este ceea ce init
și dealloc
metode arata ca atunci cand injectati centrul de notificare implicit.
#pragma mark - #pragma marca Inițializare - (instanțătip) init auto = [super init]; dacă (auto) [auto.notificareCenter addObserver: selector auto: @selector (applicationWillEnterForeground :) nume: UIApplicationWillEnterForegroundNotification object: nil]; întoarce-te; #pragma marca - #pragma marca Memory Management - (void) dealloc [_notificationCenter removeObserver: self];
Rețineți că nu folosim de sine
în dealloc
metodă. Aceasta este considerată o practică proastă, deoarece poate duce la rezultate neașteptate.
Există o problemă. Poți să-l vezi? În inițializatorul ViewController
clasa, accesăm centru de notificari
proprietate pentru a adăuga controlerul de vizualizare ca observator. În timpul inițializării, totuși, centru de notificari
proprietate nu a fost încă stabilită. Putem rezolva această problemă prin trecerea dependenței ca parametru al inițializatorului. Asa arata aceasta.
#pragma marcă - #pragma marca Inițializare - (instanțătype) initWithNotificationCenter: (NSNotificationCenter *) notificationCenter auto = [super init]; dacă auto] // setați Centrul de notificare [self setNotificationCenter: notificationCenter]; // Add Observer [auto.notificareCenter addObserver: selector auto: @selector (applicationWillEnterForeground :) nume: UIApplicationWillEnterForegroundNotification object: nil]; întoarce-te;
Pentru a face acest lucru, trebuie să actualizăm și interfața ViewController
clasă. Noi omitem centru de notificari
proprietate și adăugați o declarație de metodă pentru inițializatorul pe care l-am creat.
#import@interface ViewController: UIViewController #pragma - marca #pragma Inițializare - (instanțătype) initWithNotificationCenter: (NSNotificationCenter *) notificationCenter; @Sfârșit
În fișierul de implementare, creăm o extensie de clasă în care declarăm centru de notificari
proprietate. Procedând astfel, centru de notificari
proprietatea este privată ViewController
clasă și poate fi setat numai prin invocarea noului inițializator. Aceasta este o altă metodă bună de ținut minte, expuneți numai proprietățile care trebuie să fie publice.
#import "ViewController.h" @interface ViewController () @property (puternic, nonatomic) NSNotificationCenter * notificationCenter; @Sfârșit
Pentru a instanțiza o instanță a ViewController
clasă, ne bazăm pe inițializatorul pe care l-am creat mai devreme.
// Initialize ViewController ViewController * viewController = [[ViewController alin] initWithNotificationCenter: [NSNotificationCenter defaultCenter]];
Ce am realizat prin injectarea explicită a obiectului centrului de notificare ca dependență?
Interfața din ViewController
clasa demonstrează fără echivoc că clasa se bazează sau depinde de NSNotificationCenter
clasă. Dacă sunteți nou în dezvoltarea iOS sau OS X, acest lucru poate părea o mică victorie pentru complexitatea pe care am adăugat-o. Cu toate acestea, pe măsură ce proiectele dvs. devin mai complexe, veți învăța să apreciați fiecare claritate pe care o puteți adăuga la un proiect. Explicarea declarării dependențelor vă va ajuta în acest sens.
Când începeți să utilizați injecția de dependență, codul dvs. va deveni mult mai modular. Chiar dacă am injectat o anumită clasă în ViewController
, este posibil să se injecteze un obiect care se conformează unui protocol specific. Dacă adoptați această abordare, va fi mult mai ușor să înlocuiți o implementare cu alta.
#import@ interfață ViewController: UIViewController @property (puternic, nonatomic) id someObject; #pragma mark - #pragma marca Inițializare - (instanțătype) initWithNotificationCenter: (NSNotificationCenter *) notificationCenter; @Sfârșit
În interfața de mai sus a ViewController
clasă, noi declarăm o altă dependență. Dependența este un obiect care este conform cu MyProtocol
protocol. Aici devine evidentă adevărata putere a injecției de dependență. ViewController
clasa nu le pasă de tipul de someObject
, cere numai să adopte MyProtocol
protocol. Acest lucru conduce la coduri modulare, flexibile și testabile.
Chiar dacă testarea nu este la fel de răspândită printre dezvoltatorii iOS și OS X, așa cum este în alte comunități, testarea este un subiect cheie care câștigă în importanță și popularitate. Prin adoptarea injecției de dependență, veți face codul mult mai ușor de testat. Cum ați testa următorul inițializator? Asta va fi dificil. Dreapta?
#pragma mark - #pragma marca Inițializare - (instanțătip) init auto = [super init]; dacă (auto) NSNotificationCenter * nc = [NSNotificationCenter defaultCenter]; [nc addObserver: selector auto: @selector (applicationWillEnterForeground :) nume: UIApplicationWillEnterForegroundNotification object: nil]; întoarce-te;
Cel de-al doilea inițializator, însă, face mult mai ușor această sarcină. Uitați-vă la inițializator și la testul care merge cu el.
#pragma marcă - #pragma marca Inițializare - (instanțătype) initWithNotificationCenter: (NSNotificationCenter *) notificationCenter auto = [super init]; dacă auto] // setați Centrul de notificare [self setNotificationCenter: notificationCenter]; // Add Observer [auto.notificareCenter addObserver: selector auto: @selector (applicationWillEnterForeground :) nume: UIApplicationWillEnterForegroundNotification object: nil]; întoarce-te;
#pragma mark - #pragma mark Teste pentru initializare - (void) testInitWithNotificationCenter // Creare id de notificare mockNotificationCenter = OCMClassMock (clasa NSNotificationCenter); // Initialize ViewController * viewController = [[ViewController alloc] initWithNotificationCenter: mockNotificationCenter]; XCTAssertNotNil (viewController, @ "Controlerul de vizualizare nu trebuie să fie nul."); OCMVerificați ([mockNotificationCenter addObserver: viewController selector: @selector (applicationWillEnterForeground :) nume: UIApplicationWillEnterForegroundNotification object: nil]);
Testul de mai sus folosește biblioteca OCMock, o bibliotecă excelentă pentru Obiectiv-C. În loc să treci într - o instanță a NSNotificationCenter
, vom trece într-un obiect fals și vom verifica dacă metodele care trebuie invocate în inițializator sunt într-adevăr invocate.
Există mai multe abordări pentru a testa manipularea notificărilor și acest lucru este - de departe - cel mai ușor pe care l-am întâlnit. Se adaugă un pic de cheltuială prin injectarea obiectului centrului de notificare ca dependență, dar beneficiile depășesc complexitatea adăugată în opinia mea.
Sper că v-am convins că injecția de dependență este un concept simplu, cu o soluție simplă. Există, totuși, o serie de cadre populare și biblioteci care urmăresc să facă injectarea dependenței mai puternică și mai ușor de gestionat pentru proiecte complexe. Cele două biblioteci cele mai populare sunt Typhoon și obiecții.
Dacă sunteți nou în injecția de dependență, atunci recomandăm să începeți să utilizați tehnicile prezentate în acest tutorial. În primul rând trebuie să înțelegeți în mod corespunzător conceptul înainte de a vă baza pe o soluție terță parte, cum ar fi Typhoon sau Obiectiv.
Scopul acestui articol a fost să facă injectarea dependenței mai ușor de înțeles pentru persoanele care sunt noi în programare și care nu sunt familiarizate cu acest concept. Sper că v-am convins de valoarea injecției de dependență și de simplitatea ideii fundamentale.
Există o serie de resurse excelente cu privire la injectarea de dependență. Articolul lui James Shore despre injecția de dependență este o necesitate pentru fiecare dezvoltator. Graham Lee a scris, de asemenea, un articol grozav destinat dezvoltatorilor iOS și OS X.