Je me souviens vaguement avoir appris à l'université que le type de retour d'une méthode doit toujours être aussi restreint que possible, mais ma recherche de références en ligne est restée vide et SonarQube appelle cela une odeur de code. Par exemple. dans l'exemple suivant (notez que TreeSet
et Set
ne sont que des exemples)
Set<Number> allNumbers = integerGetter.getAll();
SonarQube me dit
Les déclarations doivent utiliser des interfaces de collecte Java telles que "List" plutôt que des classes d'implémentation spécifiques telles que "LinkedList". Le but de l'API Java Collections est de fournir une hiérarchie d'interfaces bien définie afin de masquer les détails d'implémentation. (calmar: S1319)
Je vois l'intérêt de cacher les détails de l'implémentation, car je ne peux pas facilement changer le type de retour de IntegerGetter :: getAll () sans rompre la rétrocompatibilité. Mais ce faisant, je fournis également aux consommateurs des informations potentiellement précieuses, c'est-à-dire qu'ils pourraient changer leur algorithme pour être plus approprié pour l'utilisation d'un TreeSet
. Et s'ils ne se soucient pas de cette propriété, ils peuvent toujours simplement utiliser IntegerGetter
(quelle que soit la manière dont ils l'ont obtenu), comme ceci:
public interface NumberGetter { Number get(); Set<Number> getAll(); } public class IntegerGetter implements NumberGetter { @Override public Integer get() { return 1; } @Override public TreeSet<Number> getAll() { return new TreeSet<>(Collections.singleton(1)); } }
Donc J'ai les questions suivantes:
(NB SonarQube ne se plaint pas si je remplace TreeSet
par exemple par SortedSet
. Ce code ne sent-il que le fait de ne pas utiliser une interface API Collection? s'il n'y a qu'une classe concrète pour mon problème particulier?)
3 Réponses :
J'essaie - en règle générale - d'utiliser sur la signature de méthode le type le plus général (la classe ou l'interface n'a pas d'importance) qui prend en charge l'API dont j'ai besoin: plus général le type , plus l'API est minimale.
Donc, si j'ai besoin d'un paramètre représentant une famille d'objets du même type, je commence à utiliser Collection
; si dans le scénario spécifique l'idée de commander est importante, j'utilise List
, en évitant de publier des informations sur l'implémentation spécifique de List
que j'utilise en interne : mon idée est de conserver la possibilité de modifier l'implémentation (peut être pour l'optimisation des performances, pour prendre en charge une structure de données différente, etc.) sans interrompre les clients.
Comme vous l'avez dit, publier des informations telles que J'utilise un TreSet
peut laisser la place à l'optimisation côté client - mais mon idée est que cela dépend strong >: au cas par cas, vous pouvez évaluer si le scénario spécifique nécessite d'assouplir la règle générale pour exposer l'interface plus générale que vous pouvez .
Pour en venir à vos questions:
IntegerGetter
de l'interface NumberGetter
de renvoyer un type plus étroit: Java vous permet de le faire et vous ne cassez pas mon plus règle générique est plus belle : l'interface NumberGetter
expose l'interface plus générale en utilisant Number
comme type de retour, mais dans des implémentations spécifiques, nous pouvons utiliser un type de retour plus étroit pour guider l'implémentation de la méthode: les clients se référant à l'interface la plus abstraite ne sont pas affectés par ce choix et les clients se référant à la sous-classe spécifique peuvent essayer d'utiliser l'interface plus concrète Integer
plutôt qu'à Number code> (si j'utilise explicitement un NumberGetter
, peut-être je pense en termes de Integer
s, pas en terme de Number
s), mais se référer à TreeSet
plutôt qu'à Set
n'est utile que si vous avez besoin de l'API exposée par la sous-classe et non par l'interface ...
C'est une question quasi philosophique - et ma réponse l'est aussi: j'espère que cela pourra vous être utile!
Le type de retour doit trouver un équilibre entre les besoins de l'appelant et les besoins de l'implémentation: plus vous indiquez à l'appelant le type de retour, plus il est difficile de changer ce type plus tard.
Ce qui est le plus important dépendra des circonstances spécifiques. Avez-vous déjà vu ce type changer? Quelle est la valeur de la connaissance du type de l'appelant?
Dans le cas de IntegerGetter.get ()
, il serait très surprenant que le type de retour change un jour, alors dire à l'appelant ne fait aucun mal.
Dans le cas de IntegerGetter.getAll ()
, cela dépend de ce que l'appelant utilise pour la méthode:
Iterable
serait le bon choix. Collection
pourrait. Set
. SortedSet
. TreeSet
pourrait être le bon choix. Cela ne peut pas avoir une seule réponse, car cela dépend du cas d'utilisation.
Et par là, je veux dire que cela dépend du degré de flexibilité que vous souhaitez que votre implémentation ait, ainsi que du degré de flexibilité que vous souhaitez offrir aux consommateurs de votre API
.
Je vais vous donner une réponse plus générale.
Souhaitez-vous que votre consommateur effectue uniquement une boucle? Renvoyer une Collection
.
Voulez-vous que votre consommateur puisse y accéder par index? Renvoyer une List
Voulez-vous que votre consommateur sache et puisse vérifier efficacement si un élément est présent? Renvoyer un Set
Et ainsi de suite.
La même approche est valable pour les paramètres d'entrée. Quel est l'intérêt d'accepter une List
, ou même des classes concrètes telles que ArrayList
ou LinkedList
, si vous seulement en boucle? Vous donnez simplement moins de flexibilité à votre code pour de futures modifications.
Ce que vous faites ici avec les types de retour des méthodes héritées de IntegerGetter
s'appelle spécialisation de type . Ce n'est pas grave tant que vous continuez à exposer les interfaces au monde extérieur.
Ma règle de base est d'être aussi générique que possible lorsqu'il s'agit du monde extérieur, et d'être aussi spécifique que possible lors de l'implémentation des parties critiques (principales) de mon application, pour restreindre les cas d'utilisation possibles, me protéger contre les abus Je viens de coder, et à des fins de documentation.
Ce que vous ne devriez pas faire absolument, c'est utiliser la comparaison instanceof
ou classe
pour découvrir le type réel et emprunter différentes routes. Cela ruine une base de code.
S'ils souhaitent utiliser un algorithme spécialisé, ils peuvent utiliser
instanceof
. En général, SonarQube recommande la bonne pratique correcte: utilisez toujours une interface de collecte appropriée chaque fois que possible.C'est vraiment une question de jugement. Il y a un argument selon lequel vous devez coder sur une interface, donc retourner un Set signifie que vous pouvez apporter des modifications plus tard et que vous n'êtes pas en quelque sorte vissé. D'un autre côté, si vous utilisez le getter et que vous devez continuer à travailler avec un TreeSet, cela fait vraiment partie de votre interface. Si vous n'utilisez les méthodes Set qu'après avoir appelé le getter, vous pouvez renvoyer Set en toute sécurité. Vous pouvez toujours le rendre plus précis si vous avez une raison de le faire.