L'inversion de dépendance facile (et pas chère)
Tags: xamarinforms, ioc, bonnespratiques, solid
Published: 2020-09-30 09:15:12

---
title: L'inversion de dépendance facile (et pas chère)
published: true
date: 2020-09-29 16:42:53 UTC
tags: XamarinForms,ioc,bonnespratiques,SOLID
canonical_url: https://www.sylvainmoingeon.fr/xamarinforms-inversion-de-dependance-facile-et-pas-chere/
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/j9nz30gs77j63oc8ahhb.jpg
---

Ah, l'inversion de dépendance, voilà une pratique qui est parfois difficile à faire passer dans les équipes. En particulier quand il s'agit d'applications mobiles. Les arguments sont toujours les mêmes : 

- Ça ajoute trop de complexité
- Ça bouffe des ressources et plombe les performances
- On n'en a pas besoin de toute façon on n'utilise qu'une seule implémentation
- Les frameworks c'est le mal absolu

> _Tu peux m'expliquer l'inversion de dépendance ? 
Fastoche, tu ouvres le gestionnaire de package nuget et tu installes AutoFac_

Et si on trouvait un moyen simple, efficace et sans framework d'inverser les dépendances ?

* * *

## Inversion et injection de dépendances 

Le but ici n'est pas de proposer un cours complet sur les principes d'inversion et d'injection de dépendances mais juste de présenter une astuce rapide, facile et pas chère pour effectuer une inversion de dépendance sans avoir recours à aucun framework.

Si vous ne connaissez pas ces principes, je vous invite à vous rendre sur votre moteur de recherche préféré et d'y saisir les mots clés suivants (pas tous en même temps, hein !) : [inversion de dépendance](https://lmgtfy.app/?q=inversion+de+d%C3%A9pendance) / [injection de dépendance](https://lmgtfy.app/?q=injection+d%C3%A9pendance) / [dependency inversion](https://lmgtfy.app/?q=dependency+inversion+principle) / [dependency injection](https://lmgtfy.app/?q=dependency+injection+principle) / [constructor injection](https://lmgtfy.app/?q=constructor+injection) / [ioc di](https://lmgtfy.app/?q=ioc+di).

Vous verrez qu'assez souvent cela vous ramène à l'un ou l'autre des frameworks à la mode. Et dans les cas les plus complexes, c'est pertinent de ne pas réinventer la roue. Mais dans une application mobile, on a souvent besoin de quelque chose de beaucoup plus simple sans s'encombrer d'un framework ou d'une bibliothèque supplémentaire.

## Pourquoi une inversion de contrôle ? 

Sans m'étendre sur le sujet, le but principal de l'inversion de contrôle est de ne pas laisser une classe dépendre d'autres classes. Elle ne doit dépendre que [de leurs abstractions](https://fr.wikipedia.org/wiki/Abstraction_(informatique)#Programmation_orient%C3%A9e_objet).

L'application de ce principe a des conséquences assez variées mais qui vont toutes dans le sens d'un code plus propre et mieux structuré :

- **Respect du principe de [responsabilité unique](https://fr.wikipedia.org/wiki/Principe_de_responsabilit%C3%A9_unique)** : la classe ne voit que des abstractions, la responsabilité de ce qu'il s'y passe concrètement ne la regarde pas
- **Découplage** : les classes ne sont pas liées entre elles
- **Modularité** : une classe n'étant pas liée à une implémentation particulière de ses dépendances, celle-ci est facilement interchangeable. Une même classe pourra, par exemple, lire les données dans une base Sql, un fichier json ou xml en fonction de l'implémentation du service d'accès aux données injecté.
- **Testabilité** : puisqu'on ne dépend pas d'une implémentation concrète, on peut facilement utiliser des [simulacres](https://fr.wikipedia.org/wiki/Mock_(programmation_orient%C3%A9e_objet)) pour effectuer des tests unitaires.

Pour l'astuce que je vais vous présenter très bientôt (je vous tiens en haleine !) nous allons passer les dépendances via le constructeur de la classe.

L'intérêt est double :

- **Le code est lisible** car la liste des dépendances d'une classe est directement dans la signature de son constructeur : pas de surprise cachée dans le code de la classe
- On ne peut pas oublier de passer une dépendance à la classe puisqu'on en a besoin pour l'instancier

Ici, les puristes auront sans doute les poils qui se dressent car ce que je vais vous montrer n'est pas au sens strict de l'injection de dépendance, mais pour les cas simples, ça en présente tous les avantages.

Ce que nous allons chercher à faire c'est avoir pour une classe :

- Une implémentation par défaut de ses dépendances
- La possibilité d'injecter d'autres implémentations en cas de besoin (notamment pour les tests unitaires)

## L'inversion de contrôle pas à pas 

### Partons de pas grand-chose 

Partons d'une classe ne suivant aucun pattern particulier, la dépendance est directement instanciée dans le corps de la classe. Beurk !

```csharp
public class MaClasse
{
    private readonly MaDepdendance _maDependance;

    MaClasse()
    {
        _maDependance = new MaDependance();
    }
}
```

Imaginez juste un instant que la classe `MaDependance` dépende elle-même d'une autre classe, bienvenue au [code spaghetti et aux crottes de nez dans le code](https://dev.to/sylvainmoingeon/developpez-des-applications-sans-crotte-de-nez-mc4) !

Dans un premier temps, nous allons simplement chercher à passer la dépendance depuis l'extérieur. De cette façon, notre classe n'aura plus la responsabilité de l'instancier. Rien de bien compliqué.

```csharp
public class MaClasse
{
    private readonly MaDepdendance _maDependance;

    // dépendance injectée via la constructeur
    MaClasse(MaDependance maDependance)
    {
        _maDependance = maDependance ?? throw new ArgumentNullException("Un message bien senti !");
    }
}
```

Mieux mais pas terrible. Nous n'avons en réalité fait que déplacer le problème. Pour l'instant, toujours pas de découplage ou de modularité dans le code.

### Dépendons de l'abstraction au lieu de l'implémentation 

Pour faire mieux, nous allons appliquer _stricto-sensu_ le principe d'inversion de contrôle : **dépendre d'abstraction au lieu d'implémentation**.

```csharp
public class MaClasse
{
    private readonly IMaDependance _maDependance;

    // On injecte une abstraction (interface) au lieu d'une implémentation concrète
    MaClasse(IMaDependance maDependance)
    {
        _maDependance = maDependance ?? throw new ArgumentNullException("Un message bien senti !");
    }
}
```

Vous avez vu ? Le "I" pour `interface` ? 

Ce n'est bien entendu qu'une convention de nommage, mais en réalité il s'est passé ceci :

```csharp
// interface décrivant les membres à implémenter
public interface IMaDependance
{
    MaMethode();
}

// MaDependance implémente désormais l'interface IMaDependance
public class MaDependance : IMaDependance
{
    public void MaMethode()
    {
        // implémentation de l'interface
    }
}
```

Au lieu de passer directement l'implémentation concrète de la dépendance, nous avons passé son abstraction. Chouette !

Reste un souci. Sans l'aide d'un framework qui ferait ça dynamiquement, il reste nécessaire de passer explicitement les dépendances au constructeur de notre classe lors de son instanciation.

Double problème :

1. Cela peut vite devenir très lourd si la classe a plusieurs dépendances
2. Que se passe t'il si l'on souhaite changer d'implémentation alors que celle-ci est passée en dur partout dans le code ?

### Ajoutons une implémentation par défaut 

Pour ajouter une implémentation pour défaut, nous allons simplement tirer parti du mot clé `this` appliqué au constructeur de la classe. `this` permet, en effet, d'appeler un constructeur à partir d'un autre constructeur. 

```csharp
public class MaClasse
{
    private readonly IMaDependance _maDependance;

    // Constructeur acceptant un argument
    MaClasse(IMaDependance maDependance)
    {
        _maDependance = maDependance ?? throw new ArgumentNullException("Un message bien senti !");
    }

    // Constructeur vide, qui par appelle l'autre constucteur avec 'this'
    MaClasse() : this(new MaDepdendance()){}
}
```

De cette manière, le constructeur vide appellera systématiquement le constructeur ayant la dépendance en argument en lui passant une implémentation concrète.

Nous obtenons donc deux manières d'instancier notre classe : 

- Avec le constructeur vide : la dépendance sera implémentée par défaut
- Avec l'autre constructeur : la dépendance sera implémentée _manuellement_ par le développeur

Nous avons donc atteint notre objectif premier avec un simple mot clé `this` du langage C# 

C'est bien, mais il subsiste un problème : l'implémentation par défaut est instanciée en dur dans le code :

- Si on suit les principes [SOLID](https://fr.wikipedia.org/wiki/SOLID_%28informatique%29), **une classe doit être évolutive sans modification**. Ici, ce n'est pas le cas : le jour où on aura besoin de changer d'implémentation par défaut, il sera nécessaire de modifier le code au niveau du constructeur.
- Si plusieurs classes dépendent du même service, on risque simplement d'oublier de modifier le constructeur de l'une ou l'autre des classes qui utilisent `MaDependance`.

Trouvons donc un moyen de globaliser la correspondance entre une abstraction et son implémentation par défaut. C'est-à-dire, qu'en pratique, **pour une interface donnée on obtienne systématiquement la même implémentation**.

### Prenons une bonne résolution 

Avec Xamarin.Forms nul besoin de chercher très loin, le `DependencyService` jouera ce rôle à merveille. 

> A strictement parler, le `DependencyService` sert surtout à la résolution d'implémentations natives (dans les projets iOS, Android...) pour les utiliser dans le projet commun Xamarin.Forms.  
En pratique, cela fonctionne très bien pour résoudre n'importe quel type.

Le `DependencyService` expose principalement trois méthodes : `Register`, `Get` et `Resolve`. 

`Register` ne souffre pas d’ambiguïté, c'est ici que nous enregistrerons notre implémentation par défaut.

Pour la simplicité de la démonstration, et parce qu'en pratique ce sera souvent le cas, je prends la classe `App.xaml.cs` comme point d'entrée pour enregistrer mes dépendances.

```csharp
public partial class App : Application
{
    public App()
    {
        InitializeComponent();
        
        // On enregistre le type MaDependance pour l'interface IMaDependance
        DependencyService.Register<IMaDependance, MaDependance>();
        
        MainPage = new MainPage();
    }
    
    //[...]
 }
```

Faites comme vous voulez, ce qui est important c'est de bien enregistrer les types avant toute utilisation du `DependencyService` dans le code, cela va de soi.

`Get` et `Resolve` semblent fonctionner de manière identique, cependant la documentation affichée dans Visual Studio nuance quelque peu leur usage :

_ **Resolve** : The method to use to resolve dependencies by type  
**Get** : Returns the platform-specific implementation of type T_

`Resolve` semble mieux correspondre à notre besoin.

```csharp
public class MaClasse
{
    private readonly IMaDependance _maDependance;

    MaClasse(IMaDependance maDependance)
    {
        _maDependance = maDependance ?? throw new ArgumentNullException("Un message bien senti !");
    }
    
    // On résoud le type avec le DependencyService
    MaClasse() : this(DependencyService.Resolve<IMaDependance>()){}
}
```

Désormais, notre classe ne dépend plus d'aucune implémentation concrète, elle n'est plus qu'abstraction ! 

Imaginons que de nombreuses classes dépendent de `IMaDependance` et qu'on ait besoin de changer l'implémentation de la dépendance partout dans le code, il suffira de remplacer l'implémentation enregistrée dans `DependencyService.Register`.

Pour le reste, ça n'a pas changé : pour injecter une implémentation différente il est nécessaire d'instancier la classe en passant explicitement la dépendance dans le constructeur. Ce sera notamment le cas pour les simulacres créés à fin de tests unitaires.

## Conclusion 

Si l'inversion de contrôle est souvent confondue avec le framework qui la met en oeuvre, il s'agit en réalité d'un principe à l'énoncé plutôt simple : _une classe doit dépendre d'abstractions et non d'implémentations_.

Et dans les cas simples, on peut l'appliquer **sans framework** et profiter de ses avantages à moindre frais : **découplage** , **modularité** , **testabilité**.

Je vous en ai présenté ici une façon fort simple à base d'`interface`, d'appel à un constructeur par défaut avec le mot clé `this` et du `DependencyService` de Xamarin.Forms.

> Pour un projet mobile, c'est souvent largement suffisant !

## Le code source 

Comme toujours, un petit projet d'exemple sur mon [GitHub](https://github.com/SylvainMoingeon/DITheEasyWayDemo). Celui-ci est minimaliste : un service, un ViewModel et un test unitaire. Son seul intérêt est de démontrer le fonctionnement de tout cela de façon un peu moins théorique.

{% github SylvainMoingeon/DITheEasyWayDemo %}

Et chez vous l'inversion de contrôle, ça se passe comment ?