Pourquoi définir un objet Java à l'aide d'une interface (par exemple Map) plutôt que d'une implémentation (HashMap)

17

Dans la plupart des codes Java, je vois des gens déclarer des objets Java comme ceci:

Map<String, String> hashMap = new HashMap<>();
List<String> list = new ArrayList<>();

au lieu de:

HashMap<String, String> hashMap = new HashMap<>();
ArrayList<String> list = new ArrayList<>();

Pourquoi y a-t-il une préférence pour définir l'objet Java à l'aide de l'interface plutôt que l'implémentation qui sera réellement utilisée?

Suman
la source

Réponses:

26

La raison en est que l'implémentation de ces interfaces n'est généralement pas pertinente lors de leur manipulation, donc si vous obligez l'appelant à passer un HashMapà une méthode, alors vous obligez essentiellement l'implémentation à utiliser. Donc, en règle générale, vous êtes censé gérer son interface plutôt que la mise en œuvre réelle et éviter les douleurs et les souffrances qui pourraient entraîner la modification de toutes les signatures de méthode HashMaplorsque vous décidez de les utiliser à la LinkedHashMapplace.

Il faut dire qu'il y a des exceptions à cela lorsque la mise en œuvre est pertinente. Si vous avez besoin d' une carte lorsque l' ordre est important, alors vous pouvez demander un TreeMapou LinkedHashMapà transmettre, ou mieux encore , SortedMapqui ne précise pas une implémentation spécifique. Cela oblige l'appelant à passer nécessairement un certain type d'implémentation de Map et indique fortement que l'ordre est important. Cela dit, pourriez-vous passer outre SortedMapet passer un non trié? Oui, bien sûr, mais attendez-vous à ce que de mauvaises choses se produisent.

Cependant, les meilleures pratiques dictent toujours que si ce n'est pas important, vous ne devez pas utiliser des implémentations spécifiques. C'est vrai en général. Si vous traitez Doget Catque vous en dérivez Animal, afin d'utiliser au mieux l'héritage, vous devriez généralement éviter d'avoir des méthodes spécifiques à Dogou Cat. Plutôt toutes les méthodes dans Dogou Catdevraient remplacer les méthodes dans Animalet cela vous évitera des problèmes à long terme.

Neil
la source
Lorsque vous avez besoin d'une carte triée, le type de paramètre doit être SortedMapnon TreeMap.
Céphalopode du
@Arian SortedMapest l'une des nombreuses implémentations qui traitent de la commande. C'est d'ailleurs le point. TreeMapcommande également les articles en fonction de l'implémentation de la clé Comparableou d'une Comparatorinterface donnée .
Neil
Non, SortedMap n'est pas une implémentation, c'est exactement le point. C'est l'interface pour les cartes qui trient par clé.
Céphalopode
1
@Arian Ah je vois ce que tu veux dire. Vrai, mieux SortedMap car cela ne force pas une implémentation. Je ferai les ajustements appropriés.
Neil
En fait, un LinkedHashMapne met pas en œuvre SortedMap. Les seules sous-classes de SortedMapsont ConcurrentSkipListMapet TreeMap.
bcorso
10

Selon les mots de Layman:

La même raison pour laquelle les fabricants d'appareils électriques ont construit leurs produits avec des fiches électriques au lieu de simplement décortiquer les câbles, et les maisons sont équipées de prises murales au lieu de câbles décollés qui dépassent du mur.

En utilisant des prises standard à la place, ils permettent de brancher les mêmes appareils dans n'importe quelle prise compatible dans la maison.

Du point de vue de la prise murale, peu importe si vous branchez un téléviseur ou une chaîne stéréo.

Cela rend à la fois l'appareil et la prise plus utiles.

Prenons par exemple une méthode qui accepte une carte comme argument.

La méthode fonctionnera indépendamment du fait que vous lui passiez un HashMap ou un LinkedHashMap, tant qu'il s'agit d'une sous-classe de Map.

C'est le principe de substitution de Liskov .

Dans l'exemple de code que vous avez donné, cela signifie que vous pouvez ultérieurement, pour une raison quelconque, modifier l'implémentation concrète de Hash et vous n'aurez pas besoin de modifier le reste du code.

Le problème avec le logiciel est que, comme il est relativement facile de changer les choses plus tard sans gaspiller de briques ou de mortier, les gens supposent que ce genre de prévoyance ne vaut pas la peine. Mais la réalité nous a montré que la maintenance logicielle coûte très cher.

Tulains Córdova
la source
4

C'est pour suivre le principe de ségrégation d'interface (le «je» dans SOLID ). Il empêche le code qui utilise ces objets de dépendre des méthodes des objets dont il n'a pas besoin, ce qui rend le code moins couplé et donc plus facile à modifier.

Par exemple, si vous découvrez plus tard que vous en avez vraiment besoin LinkedHashMap, vous pouvez effectuer cette modification en toute sécurité sans affecter aucun autre code.

Cependant, il y a un compromis à faire, car vous limitez artificiellement le code qui peut prendre votre objet en paramètre. Disons qu'il y a une fonction quelque part qui nécessite une HashMappour une raison quelconque. Si vous retournez un Map, vous ne pouvez pas passer votre objet dans cette fonction. Vous devez équilibrer la probabilité que dans le futur, vous ayez besoin des fonctionnalités supplémentaires de la classe la plus concrète avec le désir de limiter le couplage et de garder votre interface publique aussi petite que possible.

Karl Bielefeldt
la source
3

Le fait de contraindre la variable à une interface garantit qu'aucune des utilisations de cette variable n'utilisera HashMapdes fonctionnalités spécifiques qui peuvent ne pas exister sur l'interface, de sorte que l'instance peut être modifiée sans souci plus tard par une implémentation différente tant que la nouvelle instance implémente également la interface.

Pour cette raison, chaque fois que vous souhaitez utiliser une interface d'objets, il est toujours recommandé de déclarer vos variables comme interface et non comme implémentation particulière, cela vaut pour tous les types d'objets que vous pouvez utiliser et qui ont une interface. La raison pour laquelle vous le voyez souvent est que beaucoup de gens ont intégré cela comme une habitude.

Cela dit, il n'est pas dangereux de ne pas utiliser parfois les interfaces, et la plupart d'entre nous ne respectent pas toujours cette règle, sans réel préjudice. C'est juste une bonne pratique à respecter lorsque vous avez le sentiment que le code peut être modifié et que vous avez besoin de maintenance / croissance à l'avenir. C'est moins une préoccupation lorsque vous piratez du code que vous ne soupçonnez pas d'avoir une longue durée de vie ou qui a beaucoup d'importance. En outre, enfreindre cette règle a généralement une petite conséquence que le changement d'implémentation en une autre peut nécessiter un peu de refactoring, donc si vous ne la suivez pas toujours, vous ne vous blesserez pas beaucoup, bien qu'il n'y ait pas vraiment de mal à la suivre non plus .

Jimmy Hoffa
la source