Par exemple, j'avais vu des codes créer un fragment comme celui-ci:
Fragment myFragment=new MyFragment();
qui déclare une variable sous la forme Fragment au lieu de MyFragment, lequel MyFragment est une classe enfant de Fragment. Je ne suis pas satisfait de cette ligne de codes car je pense que ce code devrait être:
MyFragment myFragment=new MyFragment();
qui est plus précis, est-ce vrai?
Ou en général, est-ce une mauvaise pratique d'utiliser:
Parent x=new Child();
au lieu de
Child x=new Child();
si nous pouvons changer le premier en dernier sans erreur de compilation?
Parent x = new Child()
est une bonne idée.Réponses:
Cela dépend du contexte, mais je dirais que vous devriez déclarer le type le plus abstrait possible. Ainsi, votre code sera aussi général que possible et ne dépendra pas de détails non pertinents.
Un exemple serait d'avoir un
LinkedList
etArrayList
dont les deux descendentList
. Si le code fonctionne aussi bien avec n'importe quel type de liste, il n'y a aucune raison de le limiter arbitrairement à l'une des sous-classes.la source
List list = new ArrayList()
ceci: Le code utilisant la liste ne s'intéresse pas au type de liste, mais répond au contrat de l'interface Liste. À l'avenir, nous pourrions facilement passer à une autre implémentation de List et le code sous-jacent n'aurait pas besoin d'être modifié ou mis à jour.Object
!La réponse de JacquesB est correcte, bien qu'un peu abstraite. Permettez-moi de souligner plus clairement certains aspects:
Dans votre extrait de code, vous utilisez explicitement le
new
mot - clé et vous souhaitez peut-être poursuivre avec des étapes d'initialisation supplémentaires, qui sont probablement spécifiques au type concret: vous devez ensuite déclarer la variable avec ce type concret spécifique.La situation est différente lorsque vous obtenez cet objet ailleurs, par exemple
IFragment fragment = SomeFactory.GiveMeFragments(...);
. Ici, l’usine devrait normalement renvoyer le type le plus abstrait possible et le code descendant ne devrait pas dépendre des détails d’implémentation.la source
Il y a potentiellement des différences de performances.
Si la classe dérivée est scellée, le compilateur peut s'inscrire et optimiser ou éliminer ce qui serait autrement des appels virtuels. Il peut donc y avoir une différence de performance significative.
Exemple:
ToString
est un appel virtuel, maisString
est scellé. SurString
,ToString
est un non-op, donc si vous déclarez queobject
c'est un appel virtuel, si vous déclarez unString
compilateur connaît la classe dérivée n'est substituée la méthode parce que la classe est fermée, de sorte que est un non-op. Des considérations similaires sont applicables àArrayList
vsLinkedList
.Par conséquent, si vous connaissez le type concret de l'objet (et qu'il n'y a aucune raison d'encapsulation pour le masquer), vous devez le déclarer comme type. Comme vous venez de créer l'objet, vous connaissez le type concret.
la source
Parent p = new Child()
est toujours un enfant.La principale différence est le niveau d'accès requis. Et chaque bonne réponse à propos de l'abstraction implique des animaux, c'est ce que l'on me dit.
Supposons que vous ayez quelques animaux -
Cat
Bird
etDog
. Or, ces animaux ont quelques comportements communs -move()
,eat()
etspeak()
. Ils mangent tous différemment et parlent différemment, mais si j'ai besoin que mon animal mange ou parle, je ne me soucie pas vraiment de la façon dont il le fait.Mais parfois je le fais. Je n'ai jamais été un chat ou un oiseau de garde, mais j'ai un chien de garde. Alors, quand quelqu'un s'introduit chez moi, je ne peux pas me contenter de parler pour effrayer l'intrus. J'ai vraiment besoin de mon chien pour aboyer - ceci est différent de son discours, qui est juste une trame.
Donc, dans le code qui nécessite un intrus, je dois vraiment faire
Dog fido = getDog(); fido.barkMenancingly();
Mais la plupart du temps, je peux volontiers que des animaux fassent des tours où ils muent, miaulent ou tweetent pour des friandises.
Animal pet = getAnimal(); pet.speak();
Donc, pour être plus concret, ArrayList a des méthodes qui
List
ne le font pas. Notamment,trimToSize()
. Maintenant, dans 99% des cas, on s'en fiche. LeList
n'est presque jamais assez grand pour que nous nous en soucions. Mais il y a des moments où c'est. Donc, occasionnellement, nous devons spécifiquement demander à unArrayList
exécutabletrimToSize()
et rendre son tableau de sauvegarde plus petit.Notez que cela ne doit pas nécessairement être fait à la construction - cela peut être fait via une méthode de retour ou même un casting si nous en sommes absolument sûrs.
la source
De manière générale, vous devriez utiliser
ou
pour les variables locales, sauf s'il existe une bonne raison pour que la variable ait un type différent de celui avec lequel elle est initialisée.
(Ici, j'ignore le fait que le code C ++ devrait très probablement utiliser un pointeur intelligent. Il existe des cas précis et sans plusieurs lignes que je ne peux pas réellement connaître.)
L’idée générale est ici avec les langages qui prennent en charge la détection automatique des types de variables, son utilisation facilite la lecture du code et fait le bon choix (c’est-à-dire que le système de types du compilateur peut être optimisé autant que possible et changer l’initialisation pour qu’elle soit nouvelle. Le type fonctionne également si le plus abstrait est utilisé).
la source
Bien parce qu'il est facile à comprendre et à modifier ce code:
Bien car il communique l'intention:
Ok, parce que c'est commun et facile à comprendre:
La seule option qui soit vraiment mauvaise est d'utiliser
Parent
si seulementChild
a une méthodeDoSomething()
. C'est mauvais parce que cela communique mal l'intention:Voyons maintenant un peu plus le cas de la question:
Formulons cela différemment, en faisant de la seconde moitié un appel de fonction:
Cela peut être réduit à:
Ici, je suppose qu'il est largement accepté que ce
WhichType
soit le type le plus élémentaire / abstrait permettant à la fonction de fonctionner (sauf en cas de problèmes de performances). Dans ce cas, le type approprié serait soitParent
, soit quelque chose enParent
découle.Cette ligne de raisonnement explique pourquoi utiliser le type
Parent
est un bon choix, mais cela ne va pas dans le temps, les autres choix sont mauvais (ils ne le sont pas).la source
tl; dr - L' utilisation de
Child
overParent
est préférable dans le champ d'application local. Cela facilite non seulement la lisibilité, mais il est également nécessaire de s'assurer que la résolution de la méthode surchargée fonctionne correctement et contribue à une compilation efficace.Au niveau local,
Conceptuellement, il s’agit de conserver le plus d’informations possibles. Si nous rétrogradons
Parent
, nous supprimons essentiellement des informations de type qui auraient pu être utiles.Conserver les informations de type complètes présente quatre avantages principaux:
Avantage 1: Plus d'informations au compilateur
Le type apparent est utilisé dans la résolution de méthode surchargée et dans l'optimisation.
Exemple: résolution de méthode surchargée
Dans l'exemple ci-dessus, les deux
foo()
appels fonctionnent , mais dans un cas, nous obtenons la meilleure résolution de méthode surchargée.Exemple: optimisation du compilateur
Dans l'exemple ci-dessus, les deux
.Foo()
appels appellent finalement la mêmeoverride
méthode qui renvoie2
. Dans le premier cas, il existe une recherche de méthode virtuelle pour trouver la méthode correcte; cette recherche de méthode virtuelle n'est pas nécessaire dans le deuxième cas puisque celle-ci estsealed
.Nous remercions @Ben qui a fourni un exemple similaire dans sa réponse .
Avantage 2: Plus d'informations au lecteur
Connaître le type exact, c.-à-d.
Child
, Fournit plus d'informations à quiconque lit le code, ce qui permet de voir plus facilement ce que fait le programme.Bien sûr, peut - être qu'il n'a pas d' importance au code réel puisque les deux
parent.Foo();
et duchild.Foo();
sens, mais pour quelqu'un de voir le code pour la première fois, plus d' informations est tout simplement utile.De plus, en fonction de votre environnement de développement, l'EDI peut être en mesure de fournir plus d'info-bulles et de métadonnées utiles
Child
que surParent
.Avantage 3: code plus propre et plus normalisé
La plupart des exemples de code C # que j'ai vus récemment utilisent
var
, qui est essentiellement un raccourci pourChild
.Voir une
var
déclaration de non- déclaration ne fait que regarder; s'il y a une raison situationnelle pour cela, génial, mais sinon, cela semble anti-modèle.Avantage 4: logique de programme plus modifiable pour le prototypage
Les trois premiers avantages étaient les plus importants. celui-ci est beaucoup plus situationnel.
Néanmoins, pour les personnes qui utilisent du code comme d’autres utilisent Excel, nous modifions constamment notre code. Peut-être n’avons-nous pas besoin d’appeler une méthode unique
Child
dans cette version du code, mais nous pourrions réutiliser ou reformater le code ultérieurement.L'avantage d'un système de type fort est qu'il nous fournit certaines méta-données sur la logique de notre programme, rendant ainsi plus évidentes les possibilités. Ceci est incroyablement utile dans le prototypage, il est donc préférable de le conserver lorsque cela est possible.
Sommaire
L'utilisation de la
Parent
résolution des méthodes surchargée, empêche certaines optimisations du compilateur, supprime les informations du lecteur et rend le code plus laid.L'utilisation
var
est vraiment la voie à suivre. C'est rapide, propre, modelé, et aide le compilateur et l'IDE à faire leur travail correctement.Important : Cette réponse concerne environ
Parent
vs.Child
dans la portée locale d'une méthode. La question deParent
vsChild
est très différente pour les types de retour, les arguments et les champs de classe.la source
Parent list = new Child()
n'a pas beaucoup de sens. Le code qui crée directement l'instance concrète d'un objet (par opposition à l'indirection via des fabriques, des méthodes d'usine, etc.) contient des informations parfaites sur le type en cours de création, il peut être nécessaire d'interagir avec l'interface concrète pour configurer le nouvel objet, etc. La portée locale n’est pas celle où vous obtenez de la flexibilité. La flexibilité est obtenue lorsque vous exposez cet objet nouvellement créé à un client de votre classe à l'aide d'une interface plus abstraite (les usines et les méthodes d'usine en sont un exemple parfait)Parent p = new Child(); p.doSomething();
etChild c = new Child; c.doSomething();
avoir un comportement différent? Peut-être que c'est une bizarrerie dans certaines langues, mais c'est supposé être que l'objet contrôle le comportement, pas la référence. C'est la beauté de l'héritage! Tant que vous pouvez faire confiance aux implémentations enfants pour remplir le contrat de l'implémentation parent, vous pouvez continuer.new
mot - clé en C # , et lorsqu'il y a plusieurs définitions, par exemple avec l' héritage multiple en C ++ .Parent
est uninterface
.Étant donné que le
parentType foo = new childType1();
code sera limité à l’utilisation de méthodes de type parent surfoo
, il pourra toutefois stocker dansfoo
une référence tout objet dont le type dérive deparent
--not seulement des instances dechildType1
. Si la nouvellechildType1
instance est la seule chose pour laquellefoo
une référence sera jamais conservée, il peut être préférable de déclarerfoo
le type commechildType1
. D'un autre côté, iffoo
pourrait avoir besoin de contenir des références à d'autres types, le déclarer commeparentType
rendant cela possible.la source
Je suggère d'utiliser
au lieu de
Ce dernier perd des informations alors que l'ancien ne le fait pas.
Avec le premier, vous pouvez faire tout ce que vous pouvez faire avec le dernier, mais pas l'inverse.
Pour élaborer davantage le pointeur, vous devez disposer de plusieurs niveaux d’héritage.
Base
->DerivedL1
->DerivedL2
Et vous voulez initialiser le résultat de
new DerivedL2()
à une variable. L'utilisation d'un type de base vous laisse la possibilité d'utiliserBase
ouDerivedL1
.Vous pouvez utiliser
ou
Je ne vois aucun moyen logique de préférer l'un à l'autre.
Si tu utilises
il n'y a rien à discuter.
la source
Base
(en supposant que cela fonctionne). Vous préférez le plus spécifique, AFAIK la majorité préfère le moins spécifique. Je dirais que pour les variables locales, cela n'a guère d'importance.Base x = new DerivedL2();
vous êtes le plus contraint, ce qui vous permet d’échanger un (grand) enfant avec un minimum d’effort. De plus, vous voyez immédiatement que c'est possible. +++ Sommes-nous d'accord pour dire quevoid f(Base)
c'est mieux quevoid f(DerivedL2)
?Je suggérerais toujours de retourner ou de stocker les variables comme type le plus spécifique, mais d'accepter les paramètres comme types les plus larges.
Par exemple
Les paramètres sont
List<T>
un type très général.ArrayList<T>
,LinkedList<T>
et d’autres pourraient tous être acceptés par cette méthode.Fait important, le type de retour est
LinkedHashMap<K, V>
, pasMap<K, V>
. Si quelqu'un veut assigner le résultat de cette méthode à aMap<K, V>
, il peut le faire. Il indique clairement que ce résultat est plus qu'une carte, mais une carte avec un ordre défini.Supposons que cette méthode retourne
Map<K, V>
. Si l'appelant le souhaitaitLinkedHashMap<K, V>
, il devrait effectuer une vérification de type, une conversion et une gestion des erreurs, ce qui est vraiment fastidieux.la source
Map
nonLinkedHashMap
, vous voudrez seulement renvoyer unLinkedHashMap
pour une fonction similairekeyValuePairsToFifoMap
ou quelque chose comme ça. Plus large est préférable jusqu'à ce que vous ayez une raison précise de ne pas le faire.LinkedHashMap
àTreeMap
quelle qu'en soit la raison, maintenant j'ai beaucoup de choses à changer.LinkedHashMap
enTreeMap
n'est pas un changement trivial, tel que passer deArrayList
àLinkedList
. C'est un changement d'importance sémantique. Dans ce cas, vous voulez que le compilateur renvoie une erreur, forçant les utilisateurs de la valeur de retour à remarquer le changement et à prendre les mesures appropriées.Thread#getAllStackTraces()
- renvoie unMap<Thread,StackTraceElement[]>
message plutôt qu'un messageHashMap
spécifique afin qu'ils puissent le modifier àMap
leur guise.