Question

lors de la programmation en Java je pratique toujours, par habitude, écrire quelque chose comme ceci:

public List<String> foo() {
    return new ArrayList<String>();
}

La plupart du temps sans même y penser. Maintenant, la question est: dois-je toujours spécifier l'interface que le type de retour? Ou est-il conseillé d'utiliser la mise en œuvre effective de l'interface, et si oui, dans quelles circonstances?

Il est évident que l'utilisation de l'interface a beaucoup d'avantages (c'est pourquoi il est là). Dans la plupart des cas, il n'a pas vraiment d'importance ce que la mise en œuvre concrète est utilisée par une fonction de bibliothèque. Mais peut-être il y a des cas où il importe. Par exemple, si je sais que je vais surtout accéder aux données dans la liste au hasard, un LinkedList serait mauvais. Mais si ma fonction de bibliothèque ne retourne que l'interface, je ne sais tout simplement pas. Pour être du côté sûr, je pourrais même avoir besoin de copier la liste explicitement sur un ArrayList:

List bar = foo();
List myList = bar instanceof LinkedList ? new ArrayList(bar) : bar;

mais que tout semble horrible et mes collègues de travail serait probablement me lyncher dans la cafétéria. Et à juste titre.

Qu'est-ce que vous en pensez? Quelles sont vos directives, quand est-ce que vous avez tendance à la solution abstraite, et quand révélez-vous les détails de votre mise en œuvre pour des gains de performance potentiels?

Était-ce utile?

La solution

  

Par exemple, si je sais que je vais   accès principalement les données dans la liste   au hasard, un LinkedList serait mauvais.   Mais si ma fonction seule bibliothèque   retourne l'interface, je ne tout simplement pas   savoir. Pour être du côté sûr je pourrais   même pas besoin de copier la liste explicitement   vers un ArrayList.

Comme tout le monde l'a dit, vous ne devez pas se soucier de la façon dont la bibliothèque a mis en place la fonctionnalité, pour réduire le couplage et la maintenabilité de plus en plus de la bibliothèque.

Si vous, en tant que client de bibliothèque, peut démontrer que la mise en œuvre de mauvais résultats pour votre cas d'utilisation, vous pouvez contacter la personne responsable et de discuter sur la meilleure voie à suivre (une nouvelle méthode pour ce cas ou tout simplement changer la mise en œuvre).

Cela dit, votre exemple des relents d'optimisation prématurée.

Si la méthode est ou peut être critique, il peut mentionner les détails de mise en œuvre dans la documentation.

Autres conseils

Retour l'interface appropriée pour masquer les détails de mise en œuvre. Vos clients ne devraient se soucier de ce que vos offres d'objet, pas comment vous implémenté. Si vous commencez avec un ArrayList privé, et décider par la suite que quelque chose d'autre (par exemple, LinkedLisk, sauter liste, etc.) est que vous pouvez modifier la mise en œuvre plus appropriée sans affecter les clients si vous retournez l'interface. Le moment où vous revenez d'un type concret l'occasion est perdue.

Dans la programmation orientée objet, nous voulons résumer autant que possible les données. Masquer autant que possible la mise en œuvre effective, abstraire les types aussi haut que possible.

Dans ce contexte, je répondrais retour seulement ce qui est significatif . Est-ce que il est logique du tout pour la valeur de retour pour être la classe concrète? Aka dans votre exemple, demandez-vous: quelqu'un va utiliser une méthode spécifique LinkedList sur la valeur de retour de foo

  • Si non, il suffit d'utiliser l'interface de niveau supérieur. Il est beaucoup plus souple, et vous permet de changer le back-end
  • Si oui, demandez-vous: je ne peux pas factoriser mon code pour revenir à l'interface de niveau supérieur? :)

Le plus abstrait est votre code, les moins de changements vous êtes tenus de le faire lors de la modification d'un back-end. C'est aussi simple que ça.

Si, d'autre part, vous finissez par coulée les valeurs de retour à la classe concrète, bien que est un signe fort que vous devriez probablement revenir à la place la classe concrète. Vos utilisateurs / coéquipiers ne devraient pas avoir à connaître au sujet des contrats plus ou moins implicites:. Si vous avez besoin d'utiliser les méthodes concrètes, juste retourner la classe concrète, pour plus de clarté

En un mot: code abstrait , mais explicitement :)

Sans être en mesure de justifier avec des tonnes de citations CS (je suis autodidacte), je suis toujours allé par le mantra de « Acceptez le moins dérivé, retourner le plus dérivé, » lors de la conception des classes et il a résisté moi au fil des ans.

Je suppose que cela signifie en termes d'interface par rapport au rendement concret est que si vous essayez de réduire les dépendances et / ou découpler, le retour de l'interface est généralement plus utile. Toutefois, si les instruments de classe en béton plus que cette interface, il est généralement plus utile aux appelants de votre méthode pour obtenir la classe concrète retour (la « plus dérivée ») plutôt que aribtrarily les restreindre un sous-ensemble de cette fonctionnalité de l'objet retourné - sauf si vous avez réellement besoin pour les restreindre. Là encore, vous pouvez aussi simplement augmenter la couverture de l'interface. restrictions inutiles comme cela, je compare à étanchéité irréfléchie des classes; On ne sait jamais. Juste pour parler un peu de la première partie de ce mantra (pour d'autres lecteurs), en acceptant le moins dérivé donne aussi une flexibilité maximale pour les appels de méthode.

-Oisin

En général, pour une interface Parement publique tels que les API, le retour de l'interface (comme List) sur la mise en œuvre concrète (comme ArrayList) serait mieux.

L'utilisation d'un ArrayList ou LinkedList est un détail de la mise en œuvre de la bibliothèque qui devrait être pris en considération pour l'utilisation la plus courante cas de cette bibliothèque. Et bien sûr, à l'interne, pour avoir des méthodes private distribuant de LinkedLists ne serait pas nécessairement une mauvaise chose, si elle fournit des installations qui rendrait le traitement plus facile.

Il n'y a aucune raison qu'une classe concrète ne doit pas être utilisé dans la mise en œuvre, à moins qu'il y ait une bonne raison de croire qu'une autre classe List sera utilisé plus tard. Mais là encore, en changeant les détails de mise en œuvre ne devrait pas être aussi douloureux aussi longtemps que la partie exposée du public est bien conçu.

La bibliothèque elle-même devrait être une boîte noire à ses consommateurs, de sorte qu'ils ne sont pas vraiment à se soucier de ce qui se passe en interne. Cela signifie également que la bibliothèque doit être conçu de sorte qu'il est conçu pour être utilisé dans la façon prévue.

En règle générale, je ne passe en retrait implémentations internes si je suis dans certains travaux privés, intérieurs d'une bibliothèque, et même si seulement avec parcimonie. Pour tout ce qui est public et susceptible d'être appelé de l'extérieur de mon module j'utilise des interfaces, ainsi que le modèle d'usine.

Utilisation des interfaces de telle manière est avérée être un moyen très fiable d'écrire du code réutilisable.

La principale question a été déjà répondu et vous devriez toujours utiliser l'interface. Je voudrais cependant que faire des commentaires sur

  

Il est évident que l'utilisation de l'interface a beaucoup d'avantages (c'est pourquoi il est là). Dans la plupart des cas, il n'a pas vraiment d'importance ce que la mise en œuvre concrète est utilisée par une fonction de bibliothèque. Mais peut-être il y a des cas où il importe. Par exemple, si je sais que je vais surtout accéder aux données dans la liste au hasard, une LinkedList serait mauvais. Mais si ma fonction de bibliothèque ne retourne que l'interface, je ne sais tout simplement pas. Pour être du côté sûr, je pourrais même avoir besoin de copier la liste explicitement sur un ArrayList.

Si vous retournez une structure de données que vous connaissez a la mauvaise performance d'accès aléatoire - O (n) et généralement beaucoup de données - il y a d'autres interfaces, vous devez spécifieront au lieu de la liste, comme Iterable de telle sorte que toute personne utilisant la bibliothèque sera pleinement conscient que seul accès séquentiel est disponible.

Choisir le bon type de retour est pas seulement par rapport à l'interface mise en œuvre concrète, il est également sur le choix de l'interface droite.

Il n'a pas d'importance tant que ça si une méthode API retourne une interface ou une classe concrète; malgré ce que tout le monde dit ici, vous changez presque jamais la classe implementiation une fois le code écrit.

Ce qui est beaucoup plus important: toujours utiliser des interfaces minimum de portée pour la méthode paramètres ! De cette façon, les clients ont la liberté maximale et peuvent utiliser des classes votre code ne connaît même pas.

Quand une méthode API retourne ArrayList, je n'ai absolument aucun scrupule avec, mais quand il demande un paramètre ArrayList (ou, tout, Vector commun), je considère pourchassant le programmeur et lui faire du mal, car cela signifie que je ne peut pas utiliser Arrays.asList(), Collections.singletonList() ou Collections.EMPTY_LIST.

Désolé d'être en désaccord, mais je pense que la règle de base est la suivante:

  • Entrée arguments utilisent le plus générique .
  • sortie valeurs, les plus spécifique .

Alors, dans ce cas, vous voulez déclarer la mise en œuvre:

public ArrayList<String> foo() {
  return new ArrayList<String>();
}

Justification : Le cas d'entrée est déjà connue et expliquée par tout le monde: utiliser l'interface, période. Cependant, le cas de sortie peut regarder contre-intuitif. Vous voulez retourner la mise en œuvre parce que vous voulez que le client d'avoir le plus d'informations sur ce qui reçoit. Dans ce cas, plus de connaissances est plus de puissance .

Exemple 1: le client veut obtenir le 5ème élément:

  • retour Collection: doit itérer jusqu'à ce que l'élément 5 vs Liste de retour:
  • Liste de retour: list.get(4)

Exemple 2: le client veut supprimer le 5ème élément:

  • Liste de retour:. Doit créer une nouvelle liste sans l'élément spécifié (list.remove() est facultatif)
  • retour ArrayList: arrayList.remove(4)

Il est donc une grande vérité que l'utilisation d'interfaces est grand parce qu'il favorise la réutilisabilité, réduit le couplage, améliore la maintenabilité et rend les gens heureux ... mais seulement quand il est utilisé comme Entrée .

Alors, encore une fois, la règle peut être énoncée comme suit:

  • Faites preuve de souplesse pour ce que vous proposez.
  • Soyez instructif avec ce que vous livrez.

Alors, la prochaine fois, s'il vous plaît retourner la mise en œuvre.

Vous utilisez l'interface à abstraire de la mise en œuvre effective. L'interface est fondamentalement juste un plan pour ce que votre mise en œuvre peut faire.

Les interfaces sont une bonne conception, car ils vous permettent de modifier les détails de mise en œuvre sans avoir à craindre que l'une de ses consommateurs sont directement touchés, aussi longtemps que vous la mise en œuvre ne fonctionne toujours ce que l'interface dit qu'il fait.

Pour travailler avec des interfaces vous les instancier comme ceci:

IParser parser = new Parser();

IParser serait votre interface et Parser serait votre mise en œuvre. Maintenant, lorsque vous travaillez avec l'objet de l'analyseur d'en haut, vous travaillerez sur l'interface (IParser), qui à son tour à l'encontre de votre mise en œuvre (Parser).

Cela signifie que vous pouvez modifier le fonctionnement interne de Parser autant que vous voulez, il ne sera jamais affecter le code qui fonctionne contre votre interface analyseur IParser.

En utilisation générale de l'interface dans tous les cas si vous avez pas besoin de la fonctionnalité de la classe concrète. Notez que pour les listes, Java a ajouté une RandomAccess classe marqueur principalement de distinguer un cas commun où un algorithme peut-être besoin de savoir si get (i) est constante de temps ou non.

Pour les utilisations du code, Michael ci-dessus est vrai que d'être aussi générique que possible dans les paramètres de la méthode est souvent plus important encore. Cela est particulièrement vrai lors du test d'une telle méthode.

Vous trouverez (ou avez trouvé) que vous revenez interfaces, ils imprègnent dans votre code. par exemple. vous retournez une interface de la méthode A et vous Vous pour passer ensuite une interface à la méthode B.

Ce que vous faites est la programmation par contrat, mais de façon limitée.

Cela vous donne d'énormes possibilités de changer les mises en œuvre sous les couvertures (à condition que ces nouveaux objets remplissent les contrats / des comportements attendus existants).

Compte tenu de tout cela, vous avez des avantages en termes de choix de votre mise en œuvre, et comment vous pouvez remplacer les comportements (y compris les tests - en utilisant moqueuse, par exemple). Si vous ne l'aviez pas deviné, je suis pour cela et essayer de réduire autant que possible aux interfaces (ou présenter).

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top