SOLID Partie IV : Interface Segregation Principle

Introduction

Comme toujours avec les principes SOLID, l’enjeu est la maintenabilité du code. On s’intéresse en particulier au couplage. Si ce n’est pas déjà fait, je vous invite à lire SOLID Partie I : Single Responsibility Principle car les idées défendues dans l’Interface Segregation Principle (ISP) sont proches de celles du SRP. En effet, alors que le SRP défendait l’idée qu’une classe ne devrait avoir qu’une seule responsabilité (“il ne devrait y avoir qu’une seule raison de changer une classe”), l’ISP va plus loin en appliquant ce principe aux interfaces.

L’idée est de faire en sorte que chaque interface ne supporte qu’un seul rôle, et ainsi que les clients de ces interfaces n’aient pas à dépendre d’interfaces qu’ils n’utilisent pas.

Un client : une interface

Lorsque l’on cherche à appliquer des principes tels que SRP et OCP, on essaye le plus possible de manipuler des abstractions – des interfaces – au lieu de classes concrètes. Malheureusement certains problèmes peuvent se poser lorsque l’on ajoute des clients à ces interfaces. En particulier le risque concerne un phénomène de grossissement d’interface, que Robert C. Martin appelle Interface Pollution.

Prenons un exemple simple, mais qui se retrouve dans beaucoup de projets : une application dans laquelle nous rajouterons une fonctionnalité à un objet. Au départ nous avons les classes suivantes:

  • Item, un value object simple, représentant les éléments que manipule notre application
  • ItemList, une classe permettant de stocker plusieurs Item
  • ItemListUI, une classe servant à afficher un ItemList
  • ItemDAO, une classe servant à assurer la persistance.

Aucune abstraction n’est utilisée et le couplage est maximum.

Un jour on décide de changer l’implémentation de ItemList, qui utilise un tableau, pour utiliser un vecteur qui facilite quelques opérations. On décide de créer une interface pour représenter lesItemList. ItemListUI et ItemDAO ne dépendent à présent plus que de l’abstraction ItemList:

Puis on remarque que, pour être utilisée par ItemListDAO, les ItemList devraient pouvoir être serializés. De plus, ItemListUI voudrait pouvoir facilement trier un ItemList. Par chance, notre application comprend déjà deux interfaces parfaitement adaptées: Sortable et Serializable. On décide de dériver ItemList de ces deux interfaces:

Ce design ne respecte pas l’Interface Segregation Principle, et va bientôt nous poser problème. En effet, comme on a pu le constater à l’instant, les modifications d’interface viennent souvent des clients. Ainsi s’il est nécessaire que UserList soit triée dans l’autre sens et que l’on modifieSortable, la modification va se répercuter jusqu’à ItemDAO qui pourtant n’utilise pas Sortable.

De plus on pourrait décider de créer une liste d’Item ne devant/ne pouvant pas être triée. Elle ne pourrait plus dériver d’ItemList.

Une solution respectant l’ISP serait par exemple:

Chaque client manipule une interface qui lui est propre, ou tout du moins qui ne contient pas plus que nécessaire, et le couplage est minimal. C’est ainsi qu’en Java la class java.util.ArrayList<E> implémente les interfaces Serializable, Cloneable, Iterable<E>, Collection<E>, List<E> et RandomAccess, ou qu’en C# la classe System.Array implémente ICloneable, IList, ICollection, IEnumerable, IStructuralComparable et IStructuralEquatable.

Conclusion

Les grosses interfaces entraînent souvent un couplage inattendu. Les clients des interfaces n’utilisent qu’une partie de la grosse interface, mais dépendent de l’ensemble. Dans cette situation, un changement dans un client peut engendrer un changement dans l’interface, changeant les diverses implémentations de l’interface ainsi que les autres clients. L‘Interface Segregation Principle nous enseigne qu’il faut séparer les interfaces par rôle et créer si besoin une interface par client, supprimant le couplage.

Pour aller plus loin

Leave a Reply