Principe de séparation des interfaces en Java

1. Introduction

Dans ce didacticiel, nous aborderons le principe de séparation des interfaces, l'un des principes SOLID. Représentant le «I» dans «SOLID», la ségrégation des interfaces signifie simplement que nous devons diviser les interfaces plus grandes en interfaces plus petites.

Garantissant ainsi que les classes d'implémentation n'ont pas besoin d'implémenter des méthodes indésirables.

2. Principe de séparation des interfaces

Ce principe a d'abord été défini par Robert C. Martin comme: « Les clients ne doivent pas être contraints de dépendre d'interfaces qu'ils n'utilisent pas ».

L'objectif de ce principe est de réduire les effets secondaires de l'utilisation d'interfaces plus grandes en divisant les interfaces d'application en interfaces plus petites . C'est similaire au principe de responsabilité unique, où chaque classe ou interface sert un seul objectif.

Une conception précise de l'application et une abstraction correcte sont la clé du principe de séparation des interfaces. Bien que cela prenne plus de temps et d'efforts dans la phase de conception d'une application et que cela puisse augmenter la complexité du code, nous obtenons au final un code flexible.

Nous examinerons quelques exemples dans les sections suivantes où nous avons une violation du principe, puis nous réglerons le problème en appliquant correctement le principe.

3. Exemple d'interface et de mise en œuvre

Examinons une situation où nous avons une interface de paiement utilisée par une implémentation BankPayment :

public interface Payment { void initiatePayments(); Object status(); List getPayments(); }

Et la mise en œuvre:

public class BankPayment implements Payment { @Override public void initiatePayments() { // ... } @Override public Object status() { // ... } @Override public List getPayments() { // ... } }

Pour simplifier, ignorons la mise en œuvre réelle de ces méthodes dans l'entreprise.

C'est très clair - jusqu'à présent, la classe d'implémentation BankPayment a besoin de toutes les méthodes de l' interface de paiement . Ainsi, cela ne viole pas le principe.

4. Polluer l'interface

Maintenant que nous avançons dans le temps et que de nouvelles fonctionnalités arrivent, il est nécessaire d'ajouter un service LoanPayment . Ce service est également une sorte de paiement mais comporte quelques opérations supplémentaires.

Pour développer cette nouvelle fonctionnalité, nous ajouterons les nouvelles méthodes à l' interface de paiement :

public interface Payment { // original methods ... void intiateLoanSettlement(); void initiateRePayment(); }

Ensuite, nous aurons l' implémentation LoanPayment :

public class LoanPayment implements Payment { @Override public void initiatePayments() { throw new UnsupportedOperationException("This is not a bank payment"); } @Override public Object status() { // ... } @Override public List getPayments() { // ... } @Override public void intiateLoanSettlement() { // ... } @Override public void initiateRePayment() { // ... } }

Maintenant que l' interface de paiement a changé et que de nouvelles méthodes ont été ajoutées, toutes les classes d'implémentation doivent maintenant implémenter les nouvelles méthodes. Le problème est que leur mise en œuvre est indésirable et pourrait entraîner de nombreux effets secondaires. Ici, la classe d'implémentation LoanPayment doit implémenter initatePayments () sans aucun besoin réel. Et donc, le principe est violé.

Alors, qu'arrive-t-il à notre classe BankPayment :

public class BankPayment implements Payment { @Override public void initiatePayments() { // ... } @Override public Object status() { // ... } @Override public List getPayments() { // ... } @Override public void intiateLoanSettlement() { throw new UnsupportedOperationException("This is not a loan payment"); } @Override public void initiateRePayment() { throw new UnsupportedOperationException("This is not a loan payment"); } }

Notez que la mise en œuvre de BankPayment a maintenant implémenté les nouvelles méthodes. Et comme il n'en a pas besoin et n'a aucune logique pour eux, il lance simplement une exception UnsupportedOperationException . C'est là que nous commençons à violer le principe.

Dans la section suivante, nous verrons comment nous pouvons résoudre ce problème.

5. Application du principe

Dans la dernière section, nous avons intentionnellement pollué l'interface et violé le principe. Dans cette section, nous verrons comment ajouter la nouvelle fonctionnalité de remboursement de prêt sans enfreindre le principe.

Décomposons l'interface pour chaque type de paiement. La situation présente:

Notez dans le diagramme de classes, et en référence aux interfaces de la section précédente, que les méthodes status () et getPayments () sont requises dans les deux implémentations. D'un autre côté, initatePayments () n'est requis que dans BankPayment , et les méthodes initateLoanSettlement () et initiateRePayment () ne sont que pour LoanPayment .

Avec cela trié, divisons les interfaces et appliquons le principe de séparation des interfaces. Ainsi, nous avons maintenant une interface commune:

public interface Payment { Object status(); List getPayments(); }

Et deux autres interfaces pour les deux types de paiements:

public interface Bank extends Payment { void initiatePayments(); }
public interface Loan extends Payment { void intiateLoanSettlement(); void initiateRePayment(); }

Et les implémentations respectives, à commencer par BankPayment :

public class BankPayment implements Bank { @Override public void initiatePayments() { // ... } @Override public Object status() { // ... } @Override public List getPayments() { // ... } }

Et enfin, notre mise en œuvre révisée de LoanPayment :

public class LoanPayment implements Loan { @Override public void intiateLoanSettlement() { // ... } @Override public void initiateRePayment() { // ... } @Override public Object status() { // ... } @Override public List getPayments() { // ... } }

Maintenant, passons en revue le nouveau diagramme de classes:

Comme on peut le voir, les interfaces ne violent pas le principe. Les implémentations n'ont pas à fournir de méthodes vides. Cela maintient le code propre et réduit les risques de bogues.

6. Conclusion

Dans ce didacticiel, nous avons examiné un scénario simple, dans lequel nous nous sommes d'abord écartés du principe de ségrégation d'interface et avons vu les problèmes causés par cet écart. Ensuite, nous avons montré comment appliquer correctement le principe afin d'éviter ces problèmes.

Au cas où nous aurions affaire à des interfaces héritées polluées que nous ne pouvons pas modifier, le modèle d'adaptateur peut être utile.

Le principe de séparation des interfaces est un concept important lors de la conception et du développement d'applications. Le respect de ce principe permet d'éviter des interfaces gonflées aux responsabilités multiples. Cela nous aide également à suivre le principe de responsabilité unique.

Comme toujours, le code est disponible sur sur GitHub.