web-dev-qa-db-fra.com

Est-ce un anti-modèle si une propriété de classe crée et retourne une nouvelle instance d'une classe?

J'ai une classe appelée Heading qui fait quelques choses, mais elle devrait également être en mesure de retourner l'opposé de la valeur actuelle de l'en-tête, qui doit finalement être utilisée via la création d'une nouvelle instance de Heading classe elle-même.

Je peux avoir une propriété simple appelée reciprocal pour renvoyer 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 automatiquement créer une nouvelle instance de la classe Heading et la renvoyer à l'utilisateur.

Cependant, un de mes collègues m'a recommandé de simplement créer une classe propriété appelée reciprocal qui retourne 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:

  1. Dans mon esprit, une propriété d'une classe ne doit pas renvoyer une nouvelle instance d'une classe, et
  2. Le nom de la propriété, qui est reciprocal, n'aide pas le développeur à bien comprendre son comportement, sans obtenir l'aide de IDE 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.

24
53777A

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:

h2 = h1.reciprocal
h2.Name = "hello world"
h1.reciprocal.Name = ?

Ce serait bien d'avoir un avertissement que la propriété était un nouvel objet à chaque fois.

vous pouvez également supposer que:

h2.reciprocal == h1

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

22
Ewan

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 retournera 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 de Vector, RayMatrix, etc., vous pouvez faire en sorte que des getters tels que Vector.normal Et Matrix.inverse Sont à 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 utilisent Vector.create_normal() et Matrix.create_inverse() - pourtant je ne connais aucun argument solide qui puisse être avancé l'utilisation des propriétés crée une interface plus intuitive, même après avoir changé les attentes.

17
Peter

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 devrait 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 dépendances du 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.

10
Brad Thomas

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?

var reciprocalHeading = myHeading.Reciprocal;

En tant que novice en C #, mais qui a lu les Microsoft Property Usage Guidelines , je m'attendrais à ce que Reciprocal entre autres:

  1. être un membre de données logique de la classe Heading
  2. être peu coûteux à appeler, de sorte que je n'ai pas besoin de mettre en cache la valeur
  3. manque d'effets secondaires observables
  4. produire le même résultat si appelé deux fois de suite
  5. (peut-être) offrir un événement ReciprocalChanged

De ces hypothèses, (3) et (4) sont probablement correctes (en supposant que Heading est un type de valeur immuable, comme dans réponse d'Ewan ), (1) est discutable, (2) est inconnu mais également discutable, et (5) est peu susceptible d'avoir un sens sémantique (bien que quoi que ce soit has un titre devrait peut-être avoir un événement HeadingChanged). Cela me suggère que dans une API C #, "obtenir ou calculer l'inverse" devrait pas être implémenté en tant que propriété, mais surtout si le calcul bon marché et Heading est 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

Heading reciprocalHeading = myHeading.getReciprocal();

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 setReciprocal() correspondante, la convention getXXX() est maintenant un peu démodé. De Java efficace , deuxième édition (déjà plus de huit ans maintenant):

Les méthodes qui renvoient une fonction ou un attribut nonboolean de l'objet sur lequel elles sont invoquées sont généralement nommées avec un nom, une phrase nominale ou une expression verbale commençant par le verbe get…. Il existe un contingent vocal qui prétend que seule la troisième forme (commençant par get) est acceptable, mais cette affirmation est peu fondée. Les deux premiers formulaires conduisent généralement à un code plus lisible… (p. 239)

Dans une API contemporaine plus fluide, je m'attendrais donc à voir

Heading reciprocalHeading = myHeading.reciprocal();

- 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 moque.

En Ruby, une propriété n'existe pas. Il y a des "attributs", mais si je vois

reciprocalHeading = my_heading.reciprocal

Je n'ai aucun moyen immédiat de savoir si j'accède à une variable d'instance @reciprocal Via une attr_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, plutôt que de dire calcReciprocal, 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, mais

val reciprocal = heading.reciprocal

pourrait être:

// immutable public value initialized at creation time
val reciprocal: Heading = … 

// immutable public value initialized on first use
lazy val reciprocal: Heading = … 

// public method, probably recalculating on each invocation
def reciprocal: Heading = …

// as above, with parentheses that, by convention, the caller
// should only omit if they know the method has no side effects
def reciprocal(): Heading = …

(Notez que Scala permet diverses choses qui sont néanmoins découragées par le guide de style . C'est l'un de mes principaux ennuis 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 m'en fiche comment cela m'apporte la valeur.

En bref: Connaissez la langue que vous utilisez et quelles sont les attentes que les autres programmeurs apporteront à votre API. Tout le reste est un détail d'implémentation.

5
David Moles

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, dans Java je m'attendrais probablement à ce qu'il soit appelé getReciprocal();

Cela étant dit, je considérerais mutable vs immuable objets.

Avec immuable uns, les choses sont assez faciles, et cela ne fera pas de mal si vous retournez le même objet ou non.

Avec mutable uns, cela peut devenir très effrayant.

b = a.reciprocal
a += 1
b = what?

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 une meilleure dénomination qui communique ce qui se passe, par exemple createReciprocal() pourrait être un meilleur choix.

Mais cela dépend aussi du contexte.

2
Eiko

Dans 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.

1
matt freake

Ç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.

0
gnasher729

Puisque les autres réponses couvrent la partie Propriété, je vais juste répondre à votre # 2 à propos de 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 utiliser quelque chose d'aussi anodin que reverse ou opposite pourrait conduire à confusion dans certains contextes.

0
Shaz

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é.

La 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é spécifiée 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.

  • Il existe des structures BigInteger et Complex . 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 vous Heading classe.
  • Math.NET Numerics a Vector class , qui a très peu de propriétés, et semble assez proche de votre Heading. 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.

0
hyde