Principe d'inversion des dépendances
Nous voici arrivés au dernier des principes SOLID, le principe d'inversion des dépendances (DIP).
Voyons la définition qu'en donne Robert C. Martin dans son ouvrage Agile Software Development. Elle se compose de deux parties :
1. Les modules de haut niveau ne doivent pas dépendre des modules de bas niveau. Les deux doivent dépendre d'abstractions.
2. Les abstractions ne doivent pas dépendre des détails. Les détails doivent dépendre des abstractions.
Je sais que cela paraît complexe, mais si vous avez appliqué les principes ouvert/fermé et de substitution de Liskov à votre code, sachez que vous respectez déjà ce nouveau principe.
Rappelez-vous, le principe ouvert/fermé voudrait que votre application soit ouverte à l'extension et fermée à la modification. Pour y parvenir, vous avez eu recours à des interfaces proposant diverses implémentations.
Avec le principe de substitution de Liskov, vous avez appris que vous devriez pouvoir remplacer des implémentations par d'autres sans bugger votre application.
L'objectif est d'éviter la création d'un système fortement couplé dans lequel chaque module fait directement référence à des modules de niveau inférieur. C'est pour cette raison que vous devez opter pour l'abstraction afin d'obtenir un système moins couplé.
Exemple 1 : couplage fort
Créons un exemple simple, une application de notification présentant un couplage fort.
public class Email
{
public string AdresseDestinataire { get; set; }
public string Objet { get; set; }
public string Contenu { get; set; }
public void EnvoyerEmail()
{
//Envoi de l'e-mail
}
}
public class SMS
{
public string Contenu { get; set; }
public string Message { get; set; }
public void EnvoyerSMS()
{
//Envoi du SMS
}
}
public class Notification
{
private Email _email;
private SMS _sms;
public Notification()
{
_email = new Email();
_sms = new SMS();
}
public void Envoyer()
{
_email.EnvoyerEmail();
_sms.EnvoyerSMS();
}
}
Regardez de plus près la classe Notification
, une classe de niveau supérieur. Elle dépend à la fois de la classe Email
et de la classe SMS
, deux classes de niveau inférieur. On appelle cette structure une implémentation concrète. En réalité, il faudrait créer une abstraction de cette implémentation.
Votre objectif ? Découpler ces classes et réduire les dépendances.
Comment vérifier rapidement votre code pour déterminer quelles parties sont associées à un couplage fort ?
Une astuce à ne pas oublier : regardez le mot-clé new. En général, plus votre code compte d'instances du mot-clé new, plus son couplage est fort.
Pourquoi ?
Tout simplement parce que vous instanciez des objets, ce qui génère des dépendances.
Exemple 2 : refactorisation permettant de respecter le principe d'inversion des dépendances
Refactorisons cet exemple Notification
pour éliminer ces dépendances.
Tout d'abord, introduisez une abstraction fiable et que d'autres peuvent implémenter.
public interface IMessage
{
void EnvoyerMessage();
}
public class Email : IMessage
{
public string AdresseDestinataire { get; set; }
public string Objet { get; set; }
public string Contenu { get; set; }
public void EnvoyerMessage()
{
//Envoi de l'e-mail
}
}
public class SMS : IMessage
{
public string NumeroTelephone { get; set; }
public string Message { get; set; }
public void EnvoyerMessage()
{
//Envoi du sms
}
}
public class Notification
{
private ICollection<IMessage> _messages;
public Notification(ICollection<IMessage> messages)
{
this._messages = messages;
}
public void Envoyer()
{
foreach(var message in _messages)
{
message.EnvoyerMessage();
}
}
}
Analysons nos modifications.
Nous avons procédé à l'abstraction de
EnvoyerMessage()
dans une interface appeléeIMessage
.Les classes
Email
etSMS
implémentent l'interfaceIMessage
.La classe
Notification
dépend désormais de l'abstractionIMessage
et non plus de l'implémentation concrète d'une classe.
Ces modifications permettent à la classe Notification
de s'intéresser uniquement à l'existence d'une abstraction capable d'envoyer des messages.
Voilà, vous avez compris le principe d'inversion des dépendances.
Testez par vous-même !
En résumé
Le principe d'inversion des dépendances se définit comme suit :
Les modules de haut niveau ne doivent pas dépendre des modules de bas niveau. Les deux doivent dépendre des abstractions.
Les abstractions ne doivent pas dépendre des détails. Les détails doivent dépendre des abstractions.
Si vous respectez les principes ouvert/fermé et de substitution de Liskov, vos classes respecteront aussi le principe d'inversion des dépendances.
Prêtez attention au nombre de fois où vous utilisez le mot-clé new dans votre application. Cela peut être un indicateur d'un couplage trop fort.
L'objectif est de limiter les implémentations concrètes en favorisant les abstractions.
Maintenant que vous savez ce qu'il faut faire, voyons ensemble ce qu'il ne faut pas faire. Découvrez les principes STUPID pour savoir comment les éviter !