J'ai une classe appelée Heading
qui fait quelques choses, mais elle devrait également être capable de retourner l'opposé de la valeur de titre actuelle, qui doit finalement être utilisée via la création d'une nouvelle instance de la Heading
classe elle-même.
Je peux avoir une simple propriété appelée reciprocal
pour retourner l'en-tête opposé de la valeur actuelle, puis créer manuellement une nouvelle instance de la classe Heading, ou je peux créer une méthode comme createReciprocalHeading()
pour créer automatiquement une nouvelle instance de la classe Heading et la renvoyer à la utilisateur.
Cependant, un de mes collègues m'a recommandé de simplement créer une propriété de classe appelée reciprocal
qui renvoie une nouvelle instance de la classe elle-même via sa méthode getter.
Ma question est: n'est-ce pas un anti-modèle pour qu'une propriété d'une classe se comporte comme ça?
Je trouve cela particulièrement moins intuitif car:
- Dans mon esprit, une propriété d'une classe ne doit pas renvoyer une nouvelle instance d'une classe, et
- Le nom de la propriété, qui est
reciprocal
, n'aide pas le développeur à comprendre pleinement son comportement, sans obtenir l'aide de l'EDI ou vérifier la signature du getter.
Suis-je trop strict sur ce qu'une propriété de classe doit faire ou est-ce une préoccupation valable? J'ai toujours essayé de gérer l'état de la classe via ses champs et propriétés et son comportement via ses méthodes, et je ne vois pas comment cela correspond à la définition de la propriété de classe.
Heading
comme type immuable etreciprocal
renvoyer un nouveauHeading
est une bonne pratique "fosse de réussite". (avec la mise en garde lors des deux appels àreciprocal
devrait retourner "la même chose", c'est-à-dire qu'ils devraient passer le test d'égalité.)Réponses:
Ce n'est pas inconnu d'avoir des choses comme Copy () ou Clone () mais oui je pense que vous avez raison de vous inquiéter pour celui-ci.
prends pour exemple:
Ce serait bien d'avoir un avertissement que la propriété était un nouvel objet à chaque fois.
vous pouvez également supposer que:
Toutefois. Si votre classe d'en-tête était un type de valeur immuable, vous seriez en mesure d'implémenter ces relations et la réciproque pourrait être un bon nom pour l'opération
la source
Copy()
etClone()
sont des méthodes, pas de propriétés. Je pense que le PO fait référence aux getters qui retournent de nouvelles instances.String.Substring()
,Array.Reverse()
,Int32.GetHashCode()
, etc.If your heading class was an immutable value type
- Je pense que vous devez ajouter que les setters de propriétés sur les objets immuables doivent toujours renvoyer les nouvelles instances (ou du moins si la nouvelle valeur n'est pas la même que l'ancienne), car c'est la définition des objets immuables. :)Une interface d'une classe amène l'utilisateur de la classe à faire des hypothèses sur son fonctionnement.
Si bon nombre de ces hypothèses sont correctes et que quelques-unes sont fausses, l'interface est bonne.
Si beaucoup de ces hypothèses sont erronées et peu ont raison, l'interface est un déchet.
Une hypothèse courante sur les propriétés est que l'appel de la fonction get est bon marché. Une autre hypothèse courante sur les propriétés est que l'appel de la fonction get deux fois de suite renvoie la même chose.
Vous pouvez contourner ce problème en utilisant la cohérence, afin de changer les attentes. Par exemple, pour une petite bibliothèque 3D où vous avez besoin
Vector
,Ray
Matrix
etc., vous pouvez faire en sorte que les getters tels queVector.normal
etMatrix.inverse
soient à peu près toujours chers. Malheureusement, même si vos interfaces utilisent constamment des propriétés coûteuses, les interfaces seront au mieux aussi intuitives que celles qui utilisentVector.create_normal()
etMatrix.create_inverse()
- pourtant je ne connais aucun argument solide qui puisse être avancé selon lequel l'utilisation des propriétés crée une interface plus intuitive, même après avoir changé les attentes.la source
Les propriétés doivent retourner très rapidement, retourner la même valeur avec des appels répétés et obtenir leur valeur ne devrait avoir aucun effet secondaire. Ce que vous décrivez ici ne doit pas être implémenté en tant que propriété.
Votre intuition est globalement correcte. Une méthode d'usine ne renvoie pas la même valeur avec des appels répétés. Il renvoie une nouvelle instance à chaque fois. Cela le disqualifie à peu près en tant que propriété. Ce n'est pas non plus rapide si un développement ultérieur ajoute du poids à la création d'instance, par exemple les dépendances réseau.
Les propriétés doivent généralement être des opérations très simples qui récupèrent / définissent une partie de l'état actuel de la classe. Les opérations de type usine ne répondent pas à ces critères.
la source
DateTime.Now
propriété est couramment utilisée et fait référence à un nouvel objet. Je pense que le nom d'une propriété peut aider à lever l'ambiguïté quant à savoir si le même objet est retourné à chaque fois. Tout est dans le nom et comment il est utilisé.DateTime
s'agit d'un type de valeur et n'a pas de concept d'identité d'objet. Si je comprends bien, le problème est qu'il n'est pas déterministe et renvoie une nouvelle valeur à chaque fois.DateTime.New
est statique, et vous ne pouvez donc pas vous attendre à ce qu'il représente un état d'un objet.Je ne pense pas qu'il y ait une réponse indépendante de la langue à cela, car ce qui constitue une «propriété» est une question spécifique à la langue, et ce qu'un appelant d'une «propriété» attend est également une question spécifique à la langue. Je pense que la façon la plus fructueuse d'y penser est de penser à quoi cela ressemble du point de vue de l'appelant.
En C #, les propriétés se distinguent en ce qu'elles sont (conventionnellement) capitalisées (comme les méthodes) mais manquent de parenthèses (comme les variables d'instances publiques). Si vous voyez le code suivant, sans documentation, à quoi vous attendez-vous?
En tant que novice en C #, mais qui a lu les directives d'utilisation des propriétés de Microsoft , je m'attendrais
Reciprocal
à, entre autres:Heading
classeReciprocalChanged
événementParmi ces hypothèses, (3) et (4) sont probablement correctes (en supposant qu'il
Heading
s'agit d'un type de valeur immuable, comme dans la réponse d'Ewan ), (1) est discutable, (2) est inconnu mais également discutable, et (5) est peu susceptible de avoir un sens sémantique (bien que tout ce qui a un titre devrait peut-être avoir unHeadingChanged
événement). Cela me suggère que dans une API C #, «obtenir ou calculer l'inverse» ne devrait pas être implémenté en tant que propriété, mais surtout si le calcul est bon marché etHeading
immuable, c'est un cas limite.(Notez, cependant, qu'aucune de ces préoccupations n'a quoi que ce soit à voir avec le fait d'appeler la propriété crée une nouvelle instance , pas même (2). La création d'objets dans le CLR, en soi, est assez bon marché.)
En Java, les propriétés sont une convention de dénomination de méthode. Si je vois
mes attentes sont similaires à celles ci-dessus (si elles sont moins explicites): je m'attends à ce que l'appel soit bon marché, idempotent et dépourvu d'effets secondaires. Cependant, en dehors du cadre JavaBeans, le concept de «propriété» n'a pas beaucoup de sens en Java, et en particulier lorsque l'on considère une propriété immuable sans correspondant
setReciprocal()
, lagetXXX()
convention est maintenant quelque peu démodée. De Effective Java , deuxième édition (déjà plus de huit ans maintenant):Dans une API contemporaine plus fluide, je m'attendrais donc à voir
- ce qui suggérerait à nouveau que l'appel est bon marché, idempotent et manque d'effets secondaires, mais ne dirait rien si un nouveau calcul est effectué ou si un nouvel objet est créé. C'est bon; dans une bonne API, je m'en fiche.
En Ruby, une propriété n'existe pas. Il y a des «attributs», mais si je vois
Je n'ai aucun moyen immédiat de savoir si j'accède à une variable d'instance
@reciprocal
via uneattr_reader
ou une méthode d'accesseur simple, ou si j'appelle une méthode qui effectue un calcul coûteux. Le fait que le nom de la méthode soit un simple nom, bien que, plutôt que de direcalcReciprocal
, suggère, encore une fois, que l'appel est au moins bon marché et n'a probablement pas d'effets secondaires.Dans Scala, la convention de dénomination est que les méthodes avec effets secondaires prennent des parenthèses et les méthodes sans elles ne le font pas, mais
pourrait être:
(Notez que Scala autorise diverses choses qui sont néanmoins découragées par le guide de style . C'est l'une de mes principales contrariétés avec Scala.)
Le manque de parenthèses me dit que l'appel n'a pas d'effets secondaires; le nom, encore une fois, suggère que l'appel devrait être relativement bon marché. Au-delà de cela, je me fiche de savoir comment cela me donne de la valeur.
En bref: Connaissez la langue que vous utilisez et sachez quelles attentes les autres programmeurs apporteront à votre API. Tout le reste est un détail d'implémentation.
la source
Comme d'autres l'ont dit, il est assez courant de renvoyer des instances de la même classe.
La dénomination doit aller de pair avec les conventions de dénomination de la langue.
Par exemple, en Java, je m'attendrais probablement à ce qu'il soit appelé
getReciprocal();
Cela étant dit, je considérerais les objets mutables vs immuables .
Avec ceux immuables , les choses sont assez faciles, et cela ne fera pas de mal si vous retournez le même objet ou non.
Avec les mutables , cela peut devenir très effrayant.
Maintenant, à quoi fait référence b? L'inverse de la valeur d'origine d'un? Ou l'inverse du changé? Cela peut être un exemple trop court, mais vous obtenez le point.
Dans ces cas, recherchez un meilleur nom qui communique ce qui se passe, par exemple,
createReciprocal()
pourrait être un meilleur choix.Mais cela dépend aussi du contexte.
la source
getReciprocal()
, car cela "sonne" comme un getter de style JavaBeans "normal". Préférez "createXXX ()" ou éventuellement "CalculateXXX ()" qui indiquent à d'autres programmeurs ou suggèrent qu'il se passe quelque chose de différentDans l'intérêt de la responsabilité unique et de la clarté, j'aurais une ReverseHeadingFactory qui a pris l'objet et a renvoyé son revers. Cela rendrait plus clair qu'un nouvel objet était retourné et signifierait que le code pour produire l'inverse était encapsulé loin d'un autre code.
la source
Ça dépend. Je m'attends généralement à ce que les propriétés retournent des choses qui font partie d'une instance, donc retourner une instance différente de la même classe serait un peu inhabituel.
Mais par exemple, une classe de chaîne pourrait avoir les propriétés "firstWord", "lastWord", ou si elle gère Unicode "firstLetter", "lastLetter" qui seraient des objets de chaîne complets - juste généralement plus petits.
la source
Étant donné que les autres réponses couvrent la partie Propriété, je vais simplement répondre à votre # 2 sur
reciprocal
:N'utilisez rien d'autre que
reciprocal
. C'est le seul terme correct pour ce que vous décrivez (et c'est le terme formel). N'écrivez pas de logiciels incorrects pour empêcher vos développeurs d'apprendre le domaine dans lequel ils travaillent. Dans la navigation, les termes ont des significations très spécifiques et utilisent quelque chose d'aussi inoffensif que celareverse
ouopposite
pourrait conduire à la confusion dans certains contextes.la source
Logiquement, non, ce n'est pas la propriété d'une rubrique. Si tel était le cas, vous pourriez également dire que le négatif d'un nombre est la propriété de ce nombre, et franchement, cela ferait perdre à la «propriété» tout son sens, presque tout serait une propriété.
Réciproque est une fonction pure d'un titre, de même que le négatif d'un nombre est une fonction pure de ce nombre.
En règle générale, si le définir n'a pas de sens, il ne devrait probablement pas être une propriété. Le paramétrer peut toujours être interdit bien sûr, mais l'ajout d'un setter devrait être théoriquement possible et significatif.
Maintenant, dans certaines langues, il pourrait être judicieux de l'avoir comme propriété comme spécifié dans le contexte de cette langue, si cela apporte des avantages en raison de la mécanique de cette langue. Par exemple, si vous souhaitez le stocker dans une base de données, il est probablement judicieux d'en faire une propriété s'il permet une gestion automatique par le cadre ORM. Ou peut-être que c'est un langage où il n'y a pas de distinction entre une propriété et une fonction membre sans paramètre (je ne connais pas un tel langage, mais je suis sûr qu'il y en a). Il appartient ensuite au concepteur d'API et au documenteur de faire la distinction entre les fonctions et les propriétés.
Edit: Pour C # en particulier, je regarderais les classes existantes, en particulier celles de la bibliothèque standard.
BigInteger
et desComplex
structures. Mais ce sont des structures, et n'ont que des fonctions statiques, et toutes les propriétés sont de type différent. Donc, pas beaucoup d'aide à la conception pour votreHeading
classe.Vector
classe , qui a très peu de propriétés, et semble assez proche de la vôtreHeading
. Si vous vous en tenez à cela, vous auriez alors une fonction réciproque, pas une propriété.Vous voudrez peut-être regarder les bibliothèques réellement utilisées par votre code ou les classes similaires existantes. Essayez de rester cohérent, c'est généralement le meilleur, si une façon n'est pas clairement correcte et une autre mauvaise.
la source
Question très intéressante en effet. Je ne vois aucune violation de SOLID + DRY + KISS dans ce que vous proposez, mais, quand même, ça sent mauvais.
Une méthode qui renvoie une instance de la classe s'appelle un constructeur , non? vous créez donc une nouvelle instance à partir d'une méthode non constructeur. Ce n'est pas la fin du monde, mais en tant que client, je ne m'attendrais pas à ça. Cela se fait généralement dans des circonstances spécifiques: une usine ou, parfois, une méthode statique de la même classe (utile pour les singletons et pour ... les programmeurs peu orientés objet?)
Plus: que se passe-t-il si vous invoquez
getReciprocal()
l'objet retourné? vous obtenez une autre instance de la classe, qui pourrait très bien être une copie exacte de la première! Et si vous invoquezgetReciprocal()
celui-là? Des objets tentaculaires?Encore une fois: et si l'invocateur a besoin de la valeur réciproque (comme scalaire, je veux dire)?
getReciprocal()->getValue()
? nous violons la loi de Déméter pour de petits gains.la source