Modèles de conception Injection de dépendance

Bien que l'injection de dépendance soit un sujet rarement enseigné aux débutants, il s'agit d'un modèle de conception qui mérite plus d'attention. De nombreux développeurs évitent l'injection de dépendance, car ils ne savent pas ce que cela signifie ou pensent qu'ils n'en ont pas besoin..

Dans cet article, je vais essayer de vous convaincre de la valeur de l'injection de dépendance. Pour ce faire, je vais vous présenter l'injection de dépendance en vous montrant à quel point c'est simple, sous sa forme la plus simple..

1. Qu'est-ce que l'injection de dépendance??

Beaucoup de choses ont été écrites sur l'injection de dépendances et de nombreux outils et bibliothèques visent à simplifier l'injection de dépendances. Cependant, une citation rend compte de la confusion entourant l'injection de dépendance chez de nombreuses personnes.

"Injection de dépendance" est un terme de 25 dollars pour un concept de 5 cents. - James Shore

Une fois que vous aurez compris l’idée qui sous-tend l’injection de dépendance, vous comprendrez également la citation ci-dessus. Commençons par un exemple pour illustrer le concept.

Une application iOS comporte de nombreuses dépendances et votre application peut s'appuyer sur des dépendances dont vous ne connaissez même pas l'existence, c'est-à-dire que vous ne les considérez pas comme des dépendances. L'extrait de code suivant montre la mise en œuvre d'un UIViewController sous-classe nommée ViewController. L'implémentation comprend une méthode nommée saveList:. Pouvez-vous repérer la dépendance?

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

Les dépendances les plus souvent négligées sont celles sur lesquelles nous nous appuyons le plus. dans le saveList: méthode, nous stockons un tableau dans la base de données par défaut de l'utilisateur, accessible via le NSUserDefaults classe. Nous accédons à l’objet par défaut partagé en appelant le standardUserDefaults méthode. Si vous êtes un peu familiarisé avec le développement iOS ou OS X, alors vous êtes probablement familiarisé avec le NSUserDefaults classe.

Le stockage des données dans la base de données par défaut de l'utilisateur est rapide, simple et fiable. Grace à standardUserDefaults méthode, nous avons accès à la base de données par défaut de l’utilisateur de n’importe où dans le projet. La méthode retourne un singleton que nous pouvons utiliser quand et où nous voulons. La vie peut être belle.

Singleton? N'importe quand et n'importe où? Est-ce que tu sens quelque chose? Non seulement je sens une dépendance, je sens aussi une mauvaise pratique. Dans cet article, je ne veux pas ouvrir une boîte de Pandore en discutant de l'utilisation ou de la mauvaise utilisation des singletons, mais il est important de comprendre que les singletons doivent être utilisés avec parcimonie..

La plupart d'entre nous sommes devenus tellement habitués à la base de données par défaut des utilisateurs que nous ne la considérons pas comme une dépendance. Mais c'est certainement un. Il en va de même pour le centre de notification, auquel nous avons généralement accès par le singleton accessible via le defaultCenter méthode. Regardez l'exemple suivant pour plus de précisions.

#import "ViewController.h" @interface ViewController () @end @implementation ViewController #pragma mark - #pragma mark Initialisation - (type d'instance) init self = [super init]; if (self) NSNotificationCenter * nc = [NSNotificationCenter defaultCenter]; [nc addObserver: sélecteur automatique: @selector (applicationWillEnterForeground :) nom: UIApplicationWillEnterForegroundNotification object: nil];  retourner soi-même;  #pragma mark - #pragma mark Gestion de la mémoire - (void) dealloc [[NSNotificationCenter defaultCenter] removeObserver: self];  #pragma mark - #pragma mark Gestion des notifications - (void) applicationWillEnterForeground: (NSNotification *) notification //… @end

Le scénario ci-dessus est très commun. Nous ajoutons le contrôleur de vue en tant qu'observateur pour les notifications avec nom UIApplicationWillEnterForegroundNotification et l'enlever en tant qu'observateur dans le dealloc méthode de la classe. Cela ajoute une autre dépendance à la ViewController classe, une dépendance qui est souvent négligée ou ignorée.

La question que vous vous posez peut-être est "Quel est le problème?" ou mieux "Y a-t-il un problème?" Commençons par la première question.

Quel est le problème?

Sur la base des exemples ci-dessus, il peut sembler qu'il n'y a pas de problème. Ce n'est pas tout à fait vrai cependant. Le contrôleur de vue dépend sur l'objet par défaut partagé et le centre de notification par défaut pour faire son travail.

Est-ce un problème? Presque chaque objet s'appuie sur d'autres objets pour faire son travail. Le problème est que les dépendances sont implicite. Un développeur débutant dans le projet ne sait pas que le contrôleur de vue s'appuie sur ces dépendances en inspectant l'interface de classe..

Tester le ViewController la classe se révélera également délicate car nous ne contrôlons pas la NSUserDefaults et NSNotificationCenter Des classes. Regardons quelques solutions à ce problème. En d'autres termes, voyons comment l'injection de dépendance peut nous aider à résoudre ce problème.

2. Injecter des dépendances

Comme je l'ai mentionné dans l'introduction, l'injection de dépendance est un concept très simple. James Shore a écrit un excellent article sur la simplicité de l'injection de dépendance. Il y a une autre citation de James Shore sur ce qu'est l'injection de dépendance est à la base.

L'injection de dépendance consiste à donner à un objet ses variables d'instance. Vraiment. C'est tout. - James Shore

Il existe un certain nombre de moyens pour y parvenir, mais il est important de comprendre d’abord ce que la citation ci-dessus signifie. Voyons comment nous pouvons appliquer cela à la ViewController classe.

Au lieu d’accéder au centre de notification par défaut de la init méthode à travers le defaultCenter méthode de classe, nous créons une propriété pour le centre de notification dans le ViewController classe. C’est ce que l’interface mise à jour du ViewController classe ressemble après cette addition.

#importation  @interface ViewController: UIViewController @property (faible, non atomique) NSNotificationCenter * defaultCenter; @fin

Cela signifie également que nous devons faire un peu plus de travail lorsque nous initialisons une instance du ViewController classe. Comme James écrit, nous remettons le ViewController instance ses variables d'instance. C’est comme cela que l’injection de dépendance est simple. C'est un nom de fantaisie pour un concept simple.

// Initialisation de View Controller ViewController * viewController = [[ViewController alloc] init]; // Configurer View Controller [viewController setNotificationCenter: [NSNotificationCenter defaultCenter]];

À la suite de ce changement, la mise en œuvre de la ViewController changements de classe. C'est ce que le init et dealloc les méthodes ressemblent à l'injection du centre de notification par défaut.

#pragma mark - Initialisation de la marque #pragma - (instancetype) init self = [super init]; if (self) [self.notificationCenter addObserver: sélecteur libre: @selector (applicationWillEnterForeground :) nom: UIApplicationWillEnterForegroundNotification object: nil];  retourner soi-même;  #pragma mark - #pragma mark Gestion de la mémoire - (void) dealloc [_notificationCenter removeObserver: self]; 

Notez que nous n'utilisons pas soi dans le dealloc méthode. Ceci est considéré comme une mauvaise pratique car il peut conduire à des résultats inattendus.

Il y a un problème. Peux tu le repérer? Dans l'initialiseur du ViewController classe, on accède au centre de notification propriété pour ajouter le contrôleur de vue en tant qu’observateur. Au cours de l'initialisation, cependant, le centre de notification la propriété n'a pas encore été définie. Nous pouvons résoudre ce problème en passant la dépendance en tant que paramètre de l’initialiseur. Voici à quoi ça ressemble.

#pragma mark - Initialisation de la marque #pragma - (instancetype) initWithNotificationCenter: (NSNotificationCenter *) notificationCenter self = [super init]; if (self) // Définit le centre de notifications [self setNotificationCenter: notificationCenter]; // Add Observer [self.notificationCenter addObserver: sélecteur de soi: @selector (applicationWillEnterForeground :) nom: UIApplicationWillEnterForegroundNotification object: nil];  retourner soi-même; 

Pour que cela fonctionne, nous devons également mettre à jour l'interface du ViewController classe. Nous omettons le centre de notification propriété et ajouter une déclaration de méthode pour l'initialiseur que nous avons créé.

#importation  @interface ViewController: UIViewController #pragma mark - #pragma mark Initialization - (type_instance) initWithNotificationCenter: (NSNotificationCenter *) notificationCenter; @fin

Dans le fichier d’implémentation, nous créons une extension de classe dans laquelle nous déclarons centre de notification propriété. Ce faisant, le centre de notification la propriété est privée à la ViewController classe et ne peut être défini qu'en appelant le nouvel initialiseur. Ceci est une autre bonne pratique à garder à l’esprit: exposez uniquement les propriétés qui doivent être publiques..

#import "ViewController.h" @interface ViewController () @property (fort, non atomique) NSNotificationCenter * notificationCenter; @fin

Pour instancier une instance du ViewController classe, nous nous appuyons sur l'initialiseur que nous avons créé plus tôt.

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

3. avantages

Qu'avons-nous accompli en injectant explicitement l'objet du centre de notification en tant que dépendance??

Clarté

L'interface du ViewController classe montre sans équivoque que la classe repose ou dépend de la NSNotificationCenter classe. Si vous débutez dans le développement iOS ou OS X, cela peut sembler une petite victoire pour la complexité que nous avons ajoutée. Cependant, au fur et à mesure que vos projets deviennent plus complexes, vous apprendrez à apprécier chaque détail que vous pouvez ajouter à un projet. Déclarer explicitement les dépendances vous aidera avec ceci.

La modularité

Lorsque vous commencez à utiliser l'injection de dépendance, votre code devient beaucoup plus modulaire. Même si nous avons injecté une classe spécifique dans le ViewController classe, il est possible d’injecter un objet conforme à un protocole spécifique. Si vous adoptez cette approche, il deviendra beaucoup plus facile de remplacer une mise en œuvre par une autre..

#importation  @interface ViewController: UIViewController @property (strong, nonatomic) id un objet; #pragma mark - #pragma mark Initialization - (type d'instance) initWithNotificationCenter: (NSNotificationCenter *) notificationCenter; @fin

Dans l'interface ci-dessus du ViewController classe, nous déclarons une autre dépendance. La dépendance est un objet conforme à la Mon protocole protocole. C’est là que le véritable pouvoir de l’injection de dépendance devient évident. le ViewController la classe ne se soucie pas du type de un objet, il demande seulement qu'il adopte le Mon protocole protocole. Cela donne un code hautement modulaire, flexible et testable.

Essai

Même si les tests ne sont pas aussi répandus chez les développeurs iOS et OS X que dans d'autres communautés, les tests sont un sujet clé qui gagne en importance et en popularité. En adoptant l'injection de dépendance, vous rendrez votre code beaucoup plus facile à tester. Comment testeriez-vous l'initialiseur suivant? Cela va être difficile. Droite?

#pragma mark - Initialisation de la marque #pragma - (instancetype) init self = [super init]; if (self) NSNotificationCenter * nc = [NSNotificationCenter defaultCenter]; [nc addObserver: sélecteur automatique: @selector (applicationWillEnterForeground :) nom: UIApplicationWillEnterForegroundNotification object: nil];  retourner soi-même; 

Le second initialiseur, cependant, rend cette tâche beaucoup plus facile. Regardez l'initialiseur et le test qui va avec.

#pragma mark - Initialisation de la marque #pragma - (instancetype) initWithNotificationCenter: (NSNotificationCenter *) notificationCenter self = [super init]; if (self) // Définit le centre de notifications [self setNotificationCenter: notificationCenter]; // Add Observer [self.notificationCenter addObserver: sélecteur de soi: @selector (applicationWillEnterForeground :) nom: UIApplicationWillEnterForegroundNotification object: nil];  retourner soi-même; 
#pragma mark - #pragma mark Tests d'initialisation - (void) testInitWithNotificationCenter // Création du centre de notifications fictives id mockNotificationCenter = OCMClassMock ([NSNotificationCenter class]); // Initialisation de View Controller ViewController * viewController = [[ViewController alloc]] initWithNotificationCenter: mockNotificationCenter]; XCTAssertNotNil (viewController, @ "Le contrôleur de vue ne doit pas être nul."); OCMVerify ([mockNotificationCenter addObserver: sélecteur viewController: @selector (applicationWillEnterForeground :) nom: UIApplicationWillEnterForegroundNotification object: nil]); 

Le test ci-dessus utilise la bibliothèque OCMock, une excellente bibliothèque moqueuse pour Objective-C. Au lieu de passer à une instance de la NSNotificationCenter classe, nous passons dans un objet fictif et vérifions si les méthodes devant être invoquées dans l'initialiseur sont bien invoquées.

Il existe plusieurs approches pour tester le traitement des notifications, et c'est de loin le plus simple que j'ai rencontré. Cela ajoute un peu de surcharge en injectant l'objet de centre de notification comme dépendance, mais l'avantage l'emporte sur la complexité ajoutée à mon avis.

4. Solutions tierces

J'espère vous avoir convaincu que l'injection de dépendance est un concept simple avec une solution simple. Il existe cependant un certain nombre de frameworks et de bibliothèques populaires qui visent à rendre l'injection de dépendance plus puissante et plus facile à gérer pour des projets complexes. Typhoon et Objection sont les deux bibliothèques les plus populaires..

Si vous êtes nouveau dans l'injection de dépendance, je vous recommande fortement de commencer à utiliser les techniques décrites dans ce tutoriel. Vous devez d’abord bien comprendre le concept avant de faire appel à une solution tierce, telle que Typhoon ou Objection..

Conclusion

Le but de cet article était de rendre l'injection de dépendance plus facile à comprendre pour les personnes novices en programmation et peu familiarisées avec le concept. J'espère vous avoir convaincu de la valeur de l'injection de dépendance et de la simplicité de l'idée sous-jacente.

Il existe un certain nombre d'excellentes ressources sur l'injection de dépendance. L'article de James Shore sur l'injection de dépendance est une lecture incontournable pour tous les développeurs. Graham Lee a également écrit un excellent article destiné aux développeurs iOS et OS X.