Ontwerppatronen afhankelijkheid injectie

Hoewel injectie van afhankelijkheid een onderwerp is dat zelden aan beginners wordt geleerd, is het een ontwerppatroon dat meer aandacht verdient. Veel ontwikkelaars vermijden afhankelijkheidsinjectie, omdat ze niet weten wat het betekent of omdat ze denken dat ze het niet nodig hebben.

In dit artikel ga ik proberen u te overtuigen van de waarde van afhankelijkheidsinjectie. Om dit te doen, zal ik u laten kennismaken met afhankelijkheidsinjectie door u te laten zien hoe eenvoudig het is in de eenvoudigste vorm.

1. Wat is afhankelijkheid injectie?

Er is veel geschreven over afhankelijkheidsinjectie en er zijn een heleboel tools en bibliotheken die erop gericht zijn de afhankelijkheidsinjectie te vereenvoudigen. Er is echter één citaat dat de verwarring van veel mensen rond de afhankelijkheidspinctie weergeeft.

"Dependency Injection" is een termijn van 25 dollar voor een concept van 5 cent. James Shore

Zodra je het idee begrijpt dat ten grondslag ligt aan afhankelijkheid injectie, zul je ook het bovenstaande citaat begrijpen. Laten we beginnen met een voorbeeld om het concept te illustreren.

Een iOS-applicatie heeft veel afhankelijkheden en uw applicatie kan afhankelijk zijn van afhankelijkheden waarvan u niet eens op de hoogte bent, dat wil zeggen, u beschouwt ze niet als afhankelijkheden. Het volgende codefragment toont de implementatie van een UIViewController subklasse genoemd ViewController. De implementatie bevat een methode genaamd savelist:. Kun je de afhankelijkheid herkennen??

#import "ViewController.h" @interface ViewController () @end @implementation ViewController - (void) saveList: (NSArray *) list if ([list isKindOfClass: [NSArray class]]) NSUserDefaults * userDefaults = [NSUserDefaults standardUserDefaults] ; [userDefaults setObject: list forKey: @ "list"];  @end

De meest over het hoofd gezien afhankelijkheden zijn degene waar we het meest op vertrouwen. In de savelist: methode, slaan we een array op in de standaard gebruikersdatabase, toegankelijk via de NSUserDefaults klasse. We openen het object shared defaults door het standardUserDefaults methode. Als u enigszins bekend bent met de ontwikkeling van iOS of OS X, bent u waarschijnlijk bekend met de NSUserDefaults klasse.

Het opslaan van gegevens in de database met gebruikersstandaarden is snel, gemakkelijk en betrouwbaar. Dankzij de standardUserDefaults methode hebben we vanaf elke locatie in het project toegang tot de database met gebruikersstandaarden. De methode retourneert een singleton die we kunnen gebruiken waar en wanneer we maar willen. Het leven kan mooi zijn.

Singleton? Wanneer en waar maar ook? Ruik je iets? Niet alleen ruik ik een afhankelijkheid, ik ruik ook een slechte gewoonte. In dit artikel wil ik niet een blik wormen openen door het gebruik en misbruik van singletons te bespreken, maar het is belangrijk om te begrijpen dat singletons spaarzaam moeten worden gebruikt.

De meesten van ons zijn zo gewend geraakt aan de standaard gebruikersdatabase dat we het niet als een afhankelijkheid zien. Maar het is zeker een. Hetzelfde geldt voor het kennisgevingscentrum, dat we gewoonlijk benaderen via het singleton toegankelijk via de defaultCenter methode. Bekijk het volgende voorbeeld voor meer informatie.

#import "ViewController.h" @interface ViewController () @end @implementation ViewController #pragma-markering - #pragma-teken Initialisatie - (instancetype) init self = [super init]; if (self) NSNotificationCenter * nc = [NSNotificationCenter defaultCenter]; [nc addObserver: self selector: @selector (applicationWillEnterForeground :) naam: UIApplicationWillEnterForegroundNotification object: nil];  terugkeer zelf;  #pragma mark - #pragma mark Geheugenbeheer - (void) dealloc [[NSNotificationCenter defaultCenter] removeObserver: self];  # pragma-markering - # pragma-markering Aanmelding Verwerking - (ongeldige) toepassingWillEnterForeground: (NSNotification *) kennisgeving // ... @end

Het bovenstaande scenario is heel gebruikelijk. We voegen de view controller toe als waarnemer voor meldingen met naam UIApplicationWillEnterForegroundNotification en verwijder het als een waarnemer in de dealloc methode van de klas. Dit voegt een andere afhankelijkheid toe aan de ViewController klasse, een afhankelijkheid die vaak over het hoofd wordt gezien of genegeerd.

De vraag die je jezelf misschien stelt is: "Wat is het probleem?" of beter "Is er een probleem?" Laten we beginnen met de eerste vraag.

Wat is het probleem?

Op basis van bovenstaande voorbeelden lijkt het alsof er geen probleem is. Dat is echter niet helemaal waar. De weergavecontroller ligt eraan op het object Shared defaults en het standaard meldingscentrum om zijn werk te doen.

Is dat een probleem? Bijna elk object vertrouwt op andere objecten om zijn werk te doen. Het probleem is dat de afhankelijkheden zijn stilzwijgend. Een ontwikkelaar die nieuw is in het project, weet niet dat de weergavecontroller afhankelijk is van deze afhankelijkheden door de klasse-interface te inspecteren.

Het testen van de ViewController klasse zal ook lastig blijken te zijn, omdat we geen controle hebben over de NSUserDefaults en NSNotificationCenter klassen. Laten we eens kijken naar enkele oplossingen voor dit probleem. Met andere woorden, laten we zien hoe afhankelijkheid van injectie ons kan helpen dit probleem op te lossen.

2. Injecteren van afhankelijkheden

Zoals ik in de inleiding al zei, is afhankelijkheidsinjectie een heel eenvoudig concept. James Shore heeft een geweldig artikel geschreven over de eenvoud van injectie van afhankelijkheid. Er is nog een ander citaat van James Shore over wat afhankelijkheid injectie in de kern is.

Afhankelijkheidsinjectie betekent een object zijn instantievariabelen geven. Werkelijk. Dat is het. James Shore

Er zijn een aantal manieren om dit te bereiken, maar het is belangrijk om eerst te begrijpen wat de bovenstaande quote betekent. Laten we kijken hoe we dit kunnen toepassen op de ViewController klasse.

In plaats van toegang te krijgen tot het standaard meldingscentrum in de in het methode door de defaultCenter klassenmethode maken we een eigenschap voor het meldpunt in de ViewController klasse. Dit is wat de bijgewerkte interface van de ViewController klasse ziet eruit als na deze toevoeging.

#importeren  @interface ViewController: UIViewController @property (weak, nonatomic) NSNotificationCenter * defaultCenter; @einde

Dit betekent ook dat we een beetje extra werk moeten doen wanneer we een instantie van de ViewController klasse. Zoals James schrijft, geven we de ViewController bijvoorbeeld de instantievariabelen. Dat is hoe eenvoudige afhankelijkheid injectie is. Het is een mooie naam voor een eenvoudig concept.

// Initialize View Controller ViewController * viewController = [[ViewController alloc] init]; // Configureer View Controller [viewController setNotificationCenter: [NSNotificationCenter defaultCenter]];

Als gevolg van deze wijziging is de implementatie van de ViewController klassewijzigingen. Dit is wat de in het en dealloc methoden zien eruit als bij het injecteren van het standaard meldingscentrum.

#pragma mark - #pragma mark Initialisatie - (instancetype) init self = [super init]; if (self) [self.notificationCenter addObserver: self selector: @selector (applicationWillEnterForeground :) name: UIApplicationWillEnterForegroundNotification object: nil];  terugkeer zelf;  #pragma mark - #pragma mark Geheugenbeheer - (void) dealloc [_notificationCenter removeObserver: self]; 

Merk op dat we niet gebruiken zelf in de dealloc methode. Dit wordt als een slechte praktijk beschouwd, omdat dit tot onverwachte resultaten kan leiden.

Er is één probleem. Kun je het zien? In de initialisatie van de ViewController klas, we hebben toegang tot de notificatie centrum eigenschap om de view controller toe te voegen als een waarnemer. Tijdens de initialisatie is de notificatie centrum eigendom is nog niet ingesteld. We kunnen dit probleem oplossen door de afhankelijkheid als een parameter van de initializer door te geven. Dit is hoe dat eruit ziet.

#pragma mark - #pragma mark Initialisatie - (instancetype) initWithNotificationCenter: (NSNotificationCenter *) notificationCenter self = [super init]; if (self) // Set Notification Center [self setNotificationCenter: notificationCenter]; // Observer toevoegen [self.notificationCenter addObserver: self selector: @selector (applicationWillEnterForeground :) name: UIApplicationWillEnterForegroundNotification object: nil];  terugkeer zelf; 

Om dit te laten werken, moeten we ook de interface van de ViewController klasse. We laten het notificatie centrum eigenschap en voeg een methode-declaratie toe voor de initializer die we hebben gemaakt.

#importeren  @interface ViewController: UIViewController #pragma mark - #pragma mark Initialisatie - (instancetype) initWithNotificationCenter: (NSNotificationCenter *) notificationCenter; @einde

In het implementatiebestand maken we een klassenuitbreiding waarin we het notificatie centrum eigendom. Door dit te doen, de notificatie centrum eigendom is privé voor de ViewController klasse en kan alleen worden ingesteld door de nieuwe initializer aan te roepen. Dit is een andere goede gewoonte om in gedachten te houden, stel alleen eigenschappen bloot die openbaar moeten zijn.

#import "ViewController.h" @interface ViewController () @property (strong, nonatomic) NSNotificationCenter * notificationCenter; @einde

Een instance van de instantie instantiëren ViewController klasse, we vertrouwen op de initializer die we eerder hebben gemaakt.

// Initialize View Controller ViewController * viewController = [[ViewController alloc] initWithNotificationCenter: [NSNotificationCenter defaultCenter]];

3. Voordelen

Wat hebben we bereikt door het meldingscentrumobject expliciet als afhankelijkheid in te voegen?

helderheid

De interface van de ViewController klasse laat ondubbelzinnig zien dat de klasse vertrouwt of hangt af van de NSNotificationCenter klasse. Als iOS of OS X nieuw voor u is, lijkt dit misschien een kleine overwinning voor de complexiteit die we hebben toegevoegd. Naarmate uw projecten echter complexer worden, leert u alle duidelijkheid die u aan een project kunt toevoegen te waarderen. Het expliciet declareren van afhankelijkheden helpt u hierbij.

modulariteit

Wanneer u afhankelijkheidsinjectie begint te gebruiken, wordt uw code veel modulair. Hoewel we een specifieke klasse hebben geïnjecteerd in de ViewController klasse is het mogelijk om een ​​object te injecteren dat voldoet aan een specifiek protocol. Als u deze aanpak toepast, wordt het veel gemakkelijker om de ene implementatie te vervangen door een andere.

#importeren  @interface ViewController: UIViewController @property (strong, nonatomic) id someObject; #pragma mark - #pragma mark Initialisatie - (instancetype) initWithNotificationCenter: (NSNotificationCenter *) notificationCenter; @einde

In de bovenstaande interface van de ViewController klasse, we verklaren een andere afhankelijkheid. De afhankelijkheid is een object dat overeenkomt met de MyProtocol protocol. Dit is waar de echte kracht van afhankelijkheid injectie duidelijk wordt. De ViewController klas geeft niet om het type someObject, het vraagt ​​alleen dat het de MyProtocol protocol. Dit zorgt voor zeer modulaire, flexibele en toetsbare code.

testen

Hoewel testen niet zo wijdverspreid is onder iOS- en OS X-ontwikkelaars als in andere community's, is testen een belangrijk onderwerp dat steeds belangrijker en populairder wordt. Door afhankelijkheidsinjectie toe te passen, maakt u uw code veel eenvoudiger om te testen. Hoe zou u de volgende initializer testen? Dat zal lastig zijn. Rechts?

#pragma mark - #pragma mark Initialisatie - (instancetype) init self = [super init]; if (self) NSNotificationCenter * nc = [NSNotificationCenter defaultCenter]; [nc addObserver: self selector: @selector (applicationWillEnterForeground :) naam: UIApplicationWillEnterForegroundNotification object: nil];  terugkeer zelf; 

De tweede initializer maakt deze taak echter veel eenvoudiger. Bekijk de initializer en de bijbehorende test.

#pragma mark - #pragma mark Initialisatie - (instancetype) initWithNotificationCenter: (NSNotificationCenter *) notificationCenter self = [super init]; if (self) // Set Notification Center [self setNotificationCenter: notificationCenter]; // Observer toevoegen [self.notificationCenter addObserver: self selector: @selector (applicationWillEnterForeground :) name: UIApplicationWillEnterForegroundNotification object: nil];  terugkeer zelf; 
#pragma mark - #pragma mark Tests voor initialisatie - (void) testInitWithNotificationCenter // Creëer Mock Notification Center id mockNotificationCenter = OCMClassMock ([NSNotificationCenter class]); // Initialize View Controller ViewController * viewController = [[ViewController alloc] initWithNotificationCenter: mockNotificationCenter]; XCTAssertNotNil (viewController, @ "De weergavecontroller mag niet nul zijn."); OCMVerify ([mockNotificationCenter addObserver: viewController selector: @selector (applicationWillEnterForeground :) name: UIApplicationWillEnterForegroundNotification object: nil]); 

De bovenstaande test maakt gebruik van de OCMock-bibliotheek, een uitstekende spotbibliotheek voor Objective-C. In plaats van in een instantie van de NSNotificationCenter klasse, we geven een nep-object door en verifiëren of de methoden die moeten worden aangeroepen in de initializer inderdaad worden aangeroepen.

Er zijn verschillende manieren om de afhandeling van meldingen te testen en dit is verreweg de makkelijkste die ik ben tegengekomen. Het voegt een beetje overhead toe door het meldingscentrumobject als afhankelijkheid te injecteren, maar het voordeel weegt op tegen de extra complexiteit naar mijn mening.

4. Oplossingen van derden

Ik hoop dat ik je ervan heb overtuigd dat afhankelijkheidsinjectie een eenvoudig concept is met een eenvoudige oplossing. Er zijn echter een aantal populaire kaders en bibliotheken die erop gericht zijn afhankelijkheidsinjectie krachtiger en gemakkelijker te beheren voor complexe projecten te maken. De twee populairste bibliotheken zijn Typhoon en Objection.

Als u een nieuwe afhankelijkheidsinjectie bent, raad ik u ten zeerste aan om de technieken te gebruiken die in deze zelfstudie worden beschreven. U moet het concept eerst goed begrijpen voordat u vertrouwt op een oplossing van derden, zoals Typhoon of Bezwaar.

Conclusie

Het doel van dit artikel was om afhankelijkheidsinjectie begrijpelijker te maken voor mensen die niet vertrouwd zijn met programmeren en onbekend zijn met het concept. Ik hoop dat ik je overtuigd heb van de waarde van afhankelijkheidsinjectie en de eenvoud van het onderliggende idee.

Er zijn een aantal uitstekende bronnen over injectie van afhankelijkheid. Het artikel van James Shore over afhankelijkheidsinjectie is een must read voor elke ontwikkelaar. Graham Lee schreef ook een geweldig artikel gericht op iOS- en OS X-ontwikkelaars.