Introduction
Le principe de ségrégation des interfaces (Interface Segregation Principle – ISP) est le quatrième des cinq principes SOLID en programmation orientée objet. Ce principe stipule que :
“Les clients ne doivent pas être forcés à dépendre d’interfaces qu’ils n’utilisent pas.”
En d’autres termes, il vaut mieux avoir plusieurs interfaces spécifiques plutôt qu’une interface unique trop large qui impose aux classes implémentant l’interface d’avoir des méthodes inutilisées.
Une façon simple d’expliquer ce principe est d’imaginer une télécommande universelle avec 50 boutons alors que vous n’en utilisez que 5. Si chaque utilisateur devait utiliser cette télécommande géante, ce serait une perte de temps et une source de confusion. En programmation, si une classe doit implémenter des méthodes dont elle n’a pas besoin, cela mène à un code inutilement complexe et difficile à maintenir.
Pourquoi est-ce important ?
Le non-respect du ISP peut entraîner :
- Une dépendance excessive entre les classes, rendant le code moins flexible.
- Exemple : Une interface
OutilMultifonction
impose aux classes implémentant l’interface d’avoir des méthodescouper()
,poncer()
,visser()
etpeindre()
, même si un pinceau n’a besoin que depeindre()
.
- Exemple : Une interface
- Des implémentations inutiles, car les classes doivent souvent fournir des implémentations vides pour des méthodes qu’elles ne devraient pas gérer.
- Exemple : Une interface
Oiseau
avecvoler()
etnager()
. Un manchot peut nager mais ne vole pas, tandis qu’un aigle peut voler mais ne nage pas. Chaque oiseau devrait donc implémenter uniquement ce qu’il sait faire.
- Exemple : Une interface
- Une maintenance plus compliquée, car ajouter une méthode dans une interface large impacte toutes les classes qui l’implémentent.
- Exemple : Ajouter une méthode
scanner()
à une interfaceImprimanteMultifonction
forcera toutes les classes d’imprimantes (même celles sans scanner) à la gérer d’une manière ou d’une autre.
- Exemple : Ajouter une méthode
Mauvais exemple : Une interface trop large
Supposons que nous ayons une interface Oiseau
qui regroupe plusieurs comportements.
interface Oiseau {
void voler();
void nager();
}
class Aigle implements Oiseau {
public void voler() {
System.out.println("L'aigle vole haut dans le ciel");
}
public void nager() {
// L'aigle ne sait pas nager !
throw new UnsupportedOperationException("Un aigle ne nage pas");
}
}
Pourquoi est-ce une violation du ISP ?
Aigle
est forcé d’implémenter une méthodenager()
qu’il ne peut pas utiliser.- Une autre classe
Manchot
devrait aussi implémentervoler()
alors qu’un manchot ne vole pas. - Cela oblige à écrire du code inutile et peut introduire des exceptions inattendues dans le programme.
Bon exemple : Découpage des interfaces
Une meilleure approche consiste à découper cette interface en interfaces spécifiques aux capacités des oiseaux.
interface OiseauVolant {
void voler();
}
interface OiseauNageur {
void nager();
}
class Aigle implements OiseauVolant {
public void voler() {
System.out.println("L'aigle vole haut dans le ciel");
}
}
class Manchot implements OiseauNageur {
public void nager() {
System.out.println("Le manchot nage rapidement");
}
}
Pourquoi est-ce une bonne solution ?
Aigle
n’implémente que l’interfaceOiseauVolant
.Manchot
n’implémente que l’interfaceOiseauNageur
.- Le code est plus clair, plus maintenable et plus évolutif.
Autre mauvais exemple : Une interface mal conçue pour les imprimantes
interface ImprimanteMultifonction {
void imprimer();
void scanner();
void faxer();
}
class ImprimanteBasique implements ImprimanteMultifonction {
public void imprimer() {
System.out.println("Impression en cours");
}
public void scanner() {
throw new UnsupportedOperationException("Cette imprimante ne scanne pas");
}
public void faxer() {
throw new UnsupportedOperationException("Cette imprimante ne faxe pas");
}
}
Problème :
ImprimanteBasique
doit implémenter des méthodes inutiles.- Si on ajoute une méthode
photocopier()
àImprimanteMultifonction
, toutes les classes l’implémentant seront affectées.
Solution correcte : Séparer les interfaces
interface Imprimante {
void imprimer();
}
interface Scanner {
void scanner();
}
interface Fax {
void faxer();
}
class ImprimanteBasique implements Imprimante {
public void imprimer() {
System.out.println("Impression en cours");
}
}
class ImprimanteAvancee implements Imprimante, Scanner, Fax {
public void imprimer() {
System.out.println("Impression en cours");
}
public void scanner() {
System.out.println("Numérisation en cours");
}
public void faxer() {
System.out.println("Envoi du fax en cours");
}
}
Maintenant, chaque classe implémente uniquement ce dont elle a besoin, rendant le code plus clair, maintenable et évolutif.
Conclusion
Le principe de ségrégation des interfaces (ISP) est crucial pour éviter les interfaces surchargées et inutiles. Pour l’appliquer correctement :
- Privilégiez plusieurs interfaces spécifiques plutôt qu’une seule interface générale.
- Évitez d’obliger les classes à implémenter des méthodes inutiles.
- Découpez les interfaces en groupes de responsabilités cohérentes.
En respectant ce principe, vous garantissez un code plus flexible, modulaire et évolutif, rendant la maintenance bien plus simple.
Qu’en pensez-vous ? Avez-vous rencontré des interfaces trop lourdes dans vos projets ? Partagez vos expériences en commentaire !