Je vais avoir un peu d'un débat avec mon ami quant à savoir si ces deux pratiques ne sont que les deux faces d'une même pièce, ou si l'on est réellement mieux.
Nous avons une fonction qui prend un paramètre, remplir un membre, puis retourne:
Item predictPrice(Item item)
Je crois que cela fonctionne sur le même objet qui est transmis, il n'y a aucune raison de passer à renvoyer l'article.
Selon lui, il ne fait aucune différence, il serait même pas d'importance si elle créait un nouvel article et le retourner. Je suis fortement en désaccord, pour les raisons suivantes:
Si vous avez plusieurs références à l'élément passé (ou pointeurs ou autre), il alloue un nouvel objet et le renvoie est d'une importance matérielle car ces références seront incorrectes.
Dans les langages non gérés en mémoire, la fonction allouant une nouvelle instance revendique la propriété de la mémoire, et donc nous devrions implémenter une méthode de nettoyage qui est appelée à un moment donné.
L'allocation sur le tas est potentiellement coûteux, il est donc important de savoir si la fonction n'appelé que.
Par conséquent, je crois qu'il est très important d'être en mesure de voir par une signature de méthodes si elle modifie un objet, ou alloue un nouveau. Par conséquent, je crois que la fonction ne fait que modifier l'objet adoptée en, la signature doit être:
void predictPrice(Item item)
Dans tous les codebase (il est vrai C et de C codebases de non Java qui est la langue que nous travaillons) Je travaille avec le style ci-dessus a essentiellement été respecté et maintenu par des programmeurs beaucoup plus expérimentés. Il prétend que ma taille de l'échantillon et codebases collègues est petit de tous les possibles et codebases collègues, et donc mon expérience est pas vrai indicateur pour savoir si l'un est supérieur.
Alors, les pensées?
la source
Item
isclass Item ...
and nottypedef ...& Item
, the localitem
is already a copyRéponses:
C'est vraiment une question d'opinion, mais pour ce que ça vaut, je trouve trompeur de retourner l'article modifié si l'article est modifié en place. Par ailleurs, si
predictPrice
on va modifier l'élément, il doit avoir un nom qui indique qu'il va le faire (commesetPredictPrice
ou quelque).Je préférerais (dans l'ordre)
predictPrice
C'était une méthode deItem
(modulo une bonne raison pour qu'il ne soit pas, ce qui pourrait bien y avoir dans votre conception globale) qui soitRenvoyé le prix prévu, ou
Avait un nom comme à la
setPredictedPrice
placeCela
predictPrice
n'a pas changéItem
, mais a renvoyé le prix prévuCe
predictPrice
fut unevoid
méthode appeléesetPredictedPrice
Ce
predictPrice
retourthis
(pour le chainage des méthodes à d' autres méthodes sur tout cas , il est une partie de) (et a été appelésetPredictedPrice
)la source
a = doSomethingWith(b)
modifiéb
et il est revenu avec les modifications qui ont fait exploser plus tard , mon code sur cette pensée qu'il savait ce quib
avait en elle. :-)Au sein d' un paradigme de conception orientée objet, les choses ne doivent pas être en train de modifier des objets en dehors de l'objet lui - même. Toute modification de l'état d'un objet doit être effectuée via des méthodes sur l'objet.
Ainsi, en
void predictPrice(Item item)
tant que fonction membre d'une autre classe, c'est faux . Cela aurait pu être acceptable à l'époque de C, mais pour Java et C ++, les modifications d'un objet impliquent un couplage plus profond avec l'objet qui entraînerait probablement d'autres problèmes de conception en cours de route (lorsque vous refactorisez la classe et modifiez ses champs, maintenant que «PredictPrice» doit être remplacé par dans un autre fichier.De retour en arrière d' un nouvel objet, ne dispose pas des effets secondaires associés, le paramètre est passé dans pas changé. Vous (la méthode PredictPrice) ne savez pas où ce paramètre est utilisé. La
Item
clé d'un hachage est-elle quelque part? avez-vous changé son code de hachage en faisant cela? Est-ce que quelqu'un d'autre s'y accroche s'attend à ce qu'il ne change pas?Ces problèmes de conception suggèrent fortement que vous ne devriez pas modifier des objets (je plaiderais pour l'immuabilité dans de nombreux cas), et si vous le faites, les modifications de l'état devraient être contenues et contrôlées par l'objet lui-même plutôt que quelque chose sinon en dehors de la classe.
Regardons ce qui se passe si l'on tripote les champs de quelque chose dans un hachage. Prenons un peu de code:
ideone
Et j'admets que ce n'est pas le plus grand code (accéder directement aux champs), mais il sert son objectif de démontrer un problème avec les données mutables utilisées comme clé d'un HashMap, ou dans ce cas, simplement mises dans un HashSet.
La sortie de ce code est:
Ce qui s'est passé, c'est que le hashCode utilisé lors de son insertion est l'endroit où l'objet se trouve dans le hachage. La modification des valeurs utilisées pour calculer le hashCode ne recalcule pas le hachage lui-même. C'est un danger de mettre n'importe quel objet mutable comme clé d'un hachage.
Ainsi, pour revenir à l'aspect original de la question, la méthode appelée ne "sait" pas comment l'objet qu'elle obtient en tant que paramètre est utilisé. Fournir la « permet de muter l'objet » comme la seule façon de faire cela signifie qu'il ya un certain nombre de bugs subtils qui peuvent se glisser. Comme perdre des valeurs dans un hachage ... sauf si vous ajoutez plus et le hachage gets rabâché - moi confiance , c'est un bug méchant à traquer (j'ai perdu la valeur jusqu'à ce que j'ajoute 20 autres éléments à la hashMap et puis soudain, il réapparaît).
Connexe: Écraser et renvoyer la valeur de l'argument utilisé comme conditionnel d'une instruction if, à l'intérieur de la même instruction if
la source
predictPrice
vous ne devriez pas jouer directement avecItem
les membres de, mais qu'est-ce qui ne va pas avec les méthodes d'appelItem
qui le font? Si c'est le seul objet en cours de modification, vous pouvez peut-être faire valoir qu'il devrait s'agir d'une méthode de l'objet en cours de modification, mais d'autres fois, plusieurs objets (qui ne devraient certainement pas être de la même classe) sont en cours de modification, en particulier dans méthodes plutôt de haut niveau.Je suis toujours la pratique de ne pas muter l'objet de quelqu'un d'autre. C'est-à-dire, uniquement des objets mutants appartenant à la classe / struct / whathaveyou dans laquelle vous vous trouvez.
Étant donné la classe de données suivante:
C'est acceptable:
Et cela est très clair:
Mais c'est beaucoup trop boueux et mystérieux à mes goûts:
la source
La syntaxe est différente entre les différentes langues et il est inutile de montrer un morceau de code qui n'est pas spécifique à une langue car cela signifie des choses complètement différentes dans différentes langues.
En Java,
Item
est un type de référence - c'est le type de pointeurs vers des objets qui sont des instances deItem
. En C ++, ce type s'écritItem *
(la syntaxe pour la même chose est différente entre les langages). DoncItem predictPrice(Item item)
en Java est équivalent àItem *predictPrice(Item *item)
C ++. Dans les deux cas, c'est une fonction (ou méthode) qui prend un pointeur sur un objet et renvoie un pointeur sur un objet.En C ++,
Item
(sans rien d'autre) est un type d'objet (en supposant queItem
c'est le nom d'une classe). Une valeur de ce type "est" un objet etItem predictPrice(Item item)
déclare une fonction qui prend un objet et renvoie un objet. Puisque&
n'est pas utilisé, il est transmis et renvoyé par valeur. Cela signifie que l'objet est copié lorsqu'il est transmis et copié lorsqu'il est renvoyé. Il n'y a pas d'équivalent en Java car Java n'a pas de types d'objets.Alors c'est quoi? Beaucoup de choses que vous posez dans la question (par exemple, "je crois que cela fonctionne sur le même objet qui est passé ...") dépendent de la compréhension exacte de ce qui est passé et retourné ici.
la source
La convention à laquelle j'ai vu adhérer les bases de code est de préférer un style clair dans la syntaxe. Si la fonction change de valeur, préférez passer par le pointeur
Ce style se traduit par:
Appelez-le, vous voyez:
PredictPrice (& my_item);
et vous savez qu'il sera modifié par votre respect du style.
la source
Bien que je n'aie pas une préférence marquée, je voterais que le retour de l'objet entraîne une meilleure flexibilité pour une API.
Notez qu'il n'a de sens que lorsque la méthode a la liberté de renvoyer un autre objet. Si votre javadoc le dit,
@returns the same value of parameter a
c'est inutile. Mais si votre javadoc le dit@return an instance that holds the same data than parameter a
, le retour de la même instance ou d'une autre est un détail d'implémentation.Par exemple, dans mon projet actuel (Java EE), j'ai une couche métier; pour stocker dans la base de données (et attribuer un ID automatique) un employé, disons, un employé j'ai quelque chose comme
Bien sûr, aucune différence avec cette implémentation car JPA renvoie le même objet après avoir attribué l'ID. Maintenant, si je suis passé à JDBC
Maintenant à ????? J'ai trois options.
Définissez un setter pour Id, et je retournerai le même objet. Laid, car
setId
sera disponible partout.Faites un travail de réflexion intense et définissez l'identifiant dans l'
Employee
objet. Sale, mais au moins c'est limité aucreateEmployee
record.Fournissez un constructeur qui copie l'original
Employee
et accepte également leid
jeu.Récupérez l'objet à partir de la base de données (peut-être nécessaire si certaines valeurs de champ sont calculées dans la base de données ou si vous souhaitez remplir une propriété).
Maintenant, pour 1. et 2. vous retournerez peut-être la même instance, mais pour 3. et 4. vous retournerez de nouvelles instances. Si d'après le principe que vous avez établi dans votre API que la "vraie" valeur sera celle retournée par la méthode, vous avez plus de liberté.
Le seul véritable argument contre lequel je peux penser est que, en utilisant les implémentations 1. ou 2., les personnes utilisant votre API peuvent ignorer l'attribution du résultat et leur code se briserait si vous passez aux implémentations 3. ou 4.).
Quoi qu'il en soit, comme vous pouvez le voir, ma préférence est basée sur d'autres préférences personnelles (comme interdire un setter pour les ID), alors peut-être que cela ne s'appliquera pas à tout le monde.
la source
Lors du codage en Java, il faut essayer de faire correspondre l'utilisation de chaque objet de type mutable à l'un des deux modèles:
Exactement une entité est considérée comme le "propriétaire" de l'objet mutable et considère l'état de cet objet comme faisant partie du sien. D'autres entités peuvent détenir des références, mais devraient considérer la référence comme identifiant un objet qui appartient à quelqu'un d'autre.
Bien que l'objet soit mutable, personne qui en possède une référence n'est autorisé à le muter, rendant ainsi l'instance effectivement immuable (notez qu'en raison des bizarreries du modèle de mémoire de Java, il n'y a pas de bon moyen de faire en sorte qu'un objet effectivement immuable ait un thread- sécurité sémantique immuable sans ajouter un niveau supplémentaire d'indirection à chaque accès). Toute entité qui encapsule l'état à l'aide d'une référence à un tel objet et souhaite modifier l'état encapsulé doit créer un nouvel objet qui contient l'état approprié.
Je n'aime pas que les méthodes mutent l'objet sous-jacent et renvoient une référence, car elles donnent l'apparence de s'adapter au deuxième motif ci-dessus. Il y a quelques cas où l'approche peut être utile, mais le code qui va muter les objets devrait ressembler à ça; le code comme
myThing = myThing.xyz(q);
ressemble beaucoup moins à la mutation de l'objet identifiémyThing
que ne le ferait simplementmyThing.xyz(q);
.la source