Cette article est la suite d’une série de plusieurs articles sur les principes SOLID (je vous invite à relire l’article Conception d’applications SOLID pour en savoir plus). L’article précédent était consacré à LSP (Liskov Subsitution Principle).
Principe de Ségrégation des Interfaces (ISP: Interface Segregation Principle)
Présentation
Ce principe est très simple à comprendre et s’exprime de la manière suivante :
Un client ne doit jamais être forcé de dépendre d’une interface qu’il n’utilise pas (Robert C. Martin).
Lorsqu’on y réfléchi, le fait d’avoir une interface « trop grosse » va vite entraîner des problèmes d’efficacité : chaque classe implémentant l’interface devra implémenter toutes les méthodes de l’interface. Il est donc intéressant de réduire cette interface au minimum nécessaire.
De plus, si deux classes A et B dépendent d’une interface I, le fait de modifier I pour le classe A va également demander de modifier la classe B. Nous remarquons donc déjà un problème de couplage fort entre A, B et I.
L’exemple le plus parlant dans le framework .NET est très certainement celui des listes et collections génériques.
Par exemple la classe List
- IList
, - ICollection
, - IList,
- ICollection,
- IReadOnlyList
, - IReadOnlyCollection
, - IEnumerable
, - IEnumerable.
En analysant le contenu de chaque interface, vous apercevrez que chacune se rapporte à un rôle précis. Chaque interface est simple à comprendre de manière unitaire.
Réduire le contenu d’une interface va permettre de réduire le travail nécessaire pour la maintenance des classes. En effet, de petites interfaces auront des impacts moins importants sur vos classes, donc moins de modifications à reporter par exemple.
Comment mettre en pratique ISP ?
Nous venons de voir la théorie sur ISP qui est assez simple en fait.
La mise ne pratique se rapproche un peu de SRP. L’idée est de découper les interfaces en groupes fonctionnels autonomes.
Je vous invite à consulter le détail de la classe List
Un exemple concret
Je vous propose de prendre un exemple concernant la gestion de personnes et leurs anniversaires.
Nous commençons donc à écrire une classe Personne avec des propriétés comme le nom, le prénom, l’age, la date de naissance ainsi que des méthodes concernant la personne : Envoyer un message, Envoyer un SMS.
Naturellement, nous écrivons donc ceci :
[csharp]
public class Personne
{
public string Nom { get; set; }
public string Prenom { get; set; }
public DateTime DateDeNaissance { get; set; }
public int Age
{
get
{
// calcul approximatif de l’age en fonction de la date de naissance
return (int)(DateTime.Now.Subtract(DateDeNaissance).TotalDays / 365.25);
}
}
public void EnvoyerSMS(string msg)
{
// TODO
}
public void EnvoyerEmail(string msg)
{
// TODO
}
}
[/csharp]
Avec Visual Studio, vous pouvez extraire l’interface (clic-droit, refactoriser, extraire l’interface), ce qui nous donne ceci :
[csharp] interface IPersonne{
int Age { get; }
DateTime DateDeNaissance { get; set; }
void EnvoyerEmail(string msg);
void EnvoyerSMS(string msg);
string Nom { get; set; }
string Prenom { get; set; }
}
[/csharp]
A côté de cela, nous avons une classe qui s’occupe de charger et d’enregistrer les dates d’anniversaires dans un calendrier partagé.
Notre classe Calendar propose donc une méthode Add qui pourrait s’écrire comme ceci :
[csharp] public class Calendar{
public void Add(IPersonne personne) { /* TODO */ }
}
[/csharp]
A partir de ce moment, Calendar est fortement couplée a IPersonne. Or, Calendar ne va jamais utiliser les fonctions concernant l’envoi de messages. Il est donc complètement inutile de transmettre une référence de IPersonne.
Il serait donc plus intéressant de transmettre une partie de cette interface concernant uniquement la gestion des anniversaires.
Un peu plus tard dans le développement, vous ajoutez une nouvelle interface pour gérer les anniversaires de vos amis sur Facebook. L’interface en question s’écrira comme ceci :
[csharp] interface IAmiFacebook{
int Age { get; }
DateTime DateDeNaissance { get; set; }
void EnvoyerMessage(string msg);
// ici, nous avons le pseudo, pas forcément le nom complet
string Pseudo { get; set; }
}
[/csharp]
Vous souhaitez désormais utiliser Calendar pour les deux types de personnes. Comme nous l’avons vu précédent, nous avons déjà remarqué un problème avec IPersonne. L’interface est « trop grosse » par rapport à ce qui est réellement nécessaire pour Calendar.
Avec IAmiFacebook, nous remarquons qu’il y a des éléments communs que nous pouvons séparer dans une interface spécifique pour regrouper les fonctionnalités d’anniversaire :
[csharp] public interface IPersonneAnniversaire{
int Age { get; }
DateTime DateDeNaissance { get; set; }
string NomAffichage { get; }
}
[/csharp]
Les interfaces IPersonne et IAmiFacebook sont mises à jour :
[csharp] public interface IPersonne: IPersonneAnniversaire{
void EnvoyerEmail(string msg);
void EnvoyerSMS(string msg);
string Nom { get; set; }
string Prenom { get; set; }
}
public interface IAmiFacebook: IPersonneAnniversaire
{
void EnvoyerMessage(string msg);
// ici, nous avons le pseudo, pas forcément le nom complet
string Pseudo { get; set; }
}
[/csharp]
La classe Calendar pourra être modifiée pour utiliser uniquement une interface du type IPersonneAnniversaire. La méthode Add pourra alors être utilisée avec IPersonne ou IAmiFacebook.
[csharp] public class Calendar{
public void Add(IPersonneAnniversaire personne) { /* TODO */ }
}
[/csharp]
Conclusion
J’arrive à la fin de mes explications concernant ISP. J’espère que vous aurez bien compris le principe (dans le cas, contraire, je vous invite à laisser un commentaire ou à m’envoyer un email si vous souhaitez des précisions).
L’exemple que je vous ai proposé était simple mais j’espère qu’il vous aura permit de comprendre la mise en pratique du principe.
Comme d’habitude, l’ensemble du projet est disponible. Vous pouvez télécharger de démonstration le projet à l’adresse suivante : ISPDemo.
A vous désormais de vous exprimer : laissez-moi un commentaire pour expliquer si vous avez déjà rencontré des interfaces mal adaptées (trop grosses) et comment vous avez réussi à réorganiser le projet.
Pour continuer dans cette série, je vous invite à lire le prochaine article sur DIP (Depedancy Inversion Principle).