• 4 heures
  • Facile

Ce cours est visible gratuitement en ligne.

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 15/02/2023

"I" pour Interface Segregation Principle ou principe de ségrégation des interfaces

Principe de ségrégation des interfaces

C'est lors d'une mission de conseil pour Xerox en 1996 que l'ingénieur logiciel Robert C. Martin a défini le principle de ségrégation des interfaces (source en anglais) :

Aucun client ne devrait dépendre de méthodes qu'il n'utilise pas.

Ce principe est lié au principe de responsabilité unique en ce qu'il stipule que même les interfaces doivent disposer d'un seul objectif. Malheureusement, il est facile de l'oublier lors de l'ajout de fonctionnalités à une application. 

L'objectif est de réduire le nombre de modifications du code nécessaires lors de l'ajout ou la mise à jour d'une fonctionnalité. Pour ce faire, vous devez découper votre application en plusieurs composants indépendants.

Violation du principe de ségrégation des interfaces

Le risque d'introduction de bugs lié à des modifications augmente avec l'extension de votre application. Par exemple, une méthode peut être ajoutée à une interface alors même qu'elle implémente une responsabilité supplémentaire. L'interface se retrouve alors polluée ce qui risque de faire multiplier les interfaces trop volumineuses contenant des méthodes qui implémentent plusieurs responsabilités. 

Regardons un peu l'exemple suivant :

public interface ICalculatrice
{
   int Somme(int nombre1, int nombre2);

   int Soustraction(int nombre1, int nombre2);

   int Multiplication(int nombre1, int nombre2);

   int Division(int nombre1, int nombre2);

   double Puissance(int nombre, double puissance);

   double RacineCarree(double nombre);
}

public class CalculatriceBasique : ICalculatrice
{
   public int Somme(int nombre1, int nombre2)
   {
        return nombre1 + nombre2;
   }

   public int Soustraction(int nombre1, int nombre2)
   {
        return nombre1 - nombre2;
   }
   
   public int Multiplication(int nombre1, int nombre2)
   {
        return nombre1 * nombre2;
   }

   public int Division(int nombre1, int nombre2)
   {
        return nombre1 / nombre2;
   }

   public double Puissance(int nombre, double puissance)
   {
        throw new NotSupportedException();
   }

   public double RacineCarree (double nombre)
   {
        throw new NotSupportedException();
   }
}

public class CalculatriceAvancee : ICalculatrice
{
   public int Somme(int nombre1, int nombre2)
   {
        return nombre1 + nombre2;
   }

   public int Soustraction(int nombre1, int nombre2)
   {
        return nombre1 - nombre2;
   }

   public int Multiplication(int nombre1, int nombre2)
   {
        return nombre1 * nombre2;
   }

   public int Division(int nombre1, int nombre2)
   {
        return nombre1 / nombre2;
   }

   public double Puissance(int nombre, double puissance)
   {
        return Math.Pow(nombre, puissance);
   }

   public double RacineCarree (double nombre)
   {
        return Math.Sqrt(nombre);
   }
}

public class EtudiantMathBasique
{
   private CalculatriceBasique Calculatrice;

   public EtudiantMathBasique()
   {
        this.Calculatrice = new CalculatriceBasique();
   }

   public double Calculer(string operation, int operand1, int operand2)
   {
 switch (operation.ToLower())
      {
         case "addition":
            return this.Calculatrice.Somme(operand1, operand2);
         case "soustraction":
             return this.Calculatrice.Soustraction(operand1, operand2);
         case "multiplication":
            return this.Calculatrice.Multiplication(operand1, operand2);
         case "division":
            return this.Calculatrice.Division(operand1, operand2);
         default:
            throw new ArgumentException();
      }
   }
}

public class EtudiantMathAvance
{
   private CalculatriceAvancee Calculatrice;

   public EtudiantMathAvance()
   {
      this.Calculatrice = new CalculatriceAvancee();
   }
   public double Calculer(string operation, int nombre)
   {
        if (operation.ToLower() == "racinecarree")
        {
            return this.Calculatrice.RacineCarree(nombre);
        }
        else
        {
            throw new ArgumentException();
        }
    }

   public double Calculer(string operation, int operand1, int operand2)
   {
 switch (operation.ToLower())
      {
         case "addition":
            return this.Calculatrice.Somme(operand1, operand2);
         case "soustraction":
            return this.Calculatrice.Soustraction(operand1, operand2);
         case "multiplication":
            return this.Calculatrice.Multiplication(operand1, operand2);
         case "division":
            return this.Calculatrice.Division(operand1, operand2);
         case "puissance":
            return this.Calculatrice.Puissance(operand1, operand2);
         default:
            throw new ArgumentException();
      }
   }
}

Dans cet exemple, deux calculs sont effectués pour les types d'étudiants  EtudiantMathBasique  et  EtudiantMathAvance  . La classe  EtudiantMathBasique  utilise  CalculatriceBasique  et la classe  EtudiantMathAvance   CalculatriceAvancee  . Les deux calculatrices implémentent l'interface  ICalculatrice .

Si j'exécute ce code, tout se déroule normalement. Alors, quel est le problème ? Les deux calculatrices implémentent l'interface  ICalculatrice  . Les méthodes incluses dans  ICalculatrice  correspondent bien aux opérations de calcul, n'est-ce pas ?

Tout à fait. Mais regardez de plus près la classe  CalculatriceBasique  . Elle est polluée, car elle doit implémenter les méthodes  Puissance  et  RacineCarree  . La classe  CalculatriceBasique  n'a pas besoin de ces méthodes, alors pourquoi doit-elle les implémenter ? La classe  EtudiantMathBasique  ne devrait pas avoir à gérer ces méthodes et elle n'a pas besoin d'une calculatrice disposant de ces fonctions. 

Il ne s'agit que de deux méthodes, et la classe va générer de toute façon une erreur si elles sont appelées. Alors, quel est le problème ?

Eh bien, réfléchissez à l'évolution de l'application. Que se passera-t-il si  CalculatriceBasique  ou  EtudiantMathBasique  gagne des sous-classes ? Ces méthodes superflues qui polluent l'interface leur seront transmises.

Et que ferez-vous si vous souhaitez ajouter des opérations à  CalculatriceAvancee  comme le calcul du cosinus, du sinus et de la tangente ? Il vous faudra mettre à jour l'interface  ICalculatrice  , et donc ajouter de nouvelles méthodes superflues à  CalculatriceBasique .

Pour éliminer cette pollution, optez pour la ségrégation

Comment optimiser une interface volumineuse et comportant trop de fonctionnalités ? Je vous ai déjà donné la réponse plus haut : utiliser le principe de ségrégation des interfaces, qui est lié au principe de responsabilité unique. Divisons notre interface  ICalculatrice  en créant des rôles plus spécifiques. 

Nous allons corriger les définitions de l'interface sans toucher au reste de l'application.

Comment cela ?

En divisant l'interface  ICalculatrice  en deux :

public interface IArithmetique
{
   int Somme(int nombre1, int nombre2);

   int Soustraction(int nombre1, int nombre2);

   int Multiplication(int nombre1, int nombre2);

   int Division(int nombre1, int nombre2);
}

public interface IExposants
{
   double Puissance(int nombre, double puissance);

   double RacineCarree(double nombre);
}

Ensuite, remplaçons l'ancien code de  CalculatriceBasique  :

(public class CalculatriceBasique : ICalculatrice)

par :

public class CalculatriceBasique : IArithmetique

Vous avez ainsi dépollué la classe  CalculatriceBasique  en lui retirant toutes les méthodes qu'elle n'utilisera jamais.

Ensuite, remplacez le code de  CalculatriceAvancee

(public class CalculatriceAvancee : ICalculatrice)

par :

public class CalculatriceAvancee : IArithmetique, IExposants

Désormais, la classe  CalculatriceAvancee  implémente les fonctionnalités arithmétiques et exponentielles des interfaces qu'elle utilisera.

En allégeant autant que possible vos interfaces, vous éviterez beaucoup de sources de pollution.

Testez par vous-même !

https://api.next.tech/api/v1/publishable_key/2A9CAA3419124E3E8C3F5AFCE5306292?content_id=c09438bd-10b2-4459-9a55-34177dea01d1&first_name={{first_name}}&last_name={{last_name}}&email={{email}}&user_id={{email}}&security_check={{date_created}}&provider=thinkific_mm 

En résumé 

  • D'après le principe de ségrégation des interfaces (ISP) "aucun client ne devrait dépendre de méthodes qu'il n'utilise pas".

  • L'objectif est d'éviter de polluer les interfaces et de les laisser devenir trop volumineuses.

  • Cette pollution se produit lorsqu'un objet implémente une interface disposant de comportements superflus.

  • Simplifiez les interfaces trop volumineuses en procédant à une ségrégation de leurs responsabilités. 

  • Respectez le principe de ségrégation des interfaces améliore la flexibilité et la maintenabilité à long terme de votre application.

Passons enfin à la dernière lettre de notre acronyme, le "D", pour principe d'inversion des dépendances.

Exemple de certificat de réussite
Exemple de certificat de réussite