Quelle est l'idée derrière le nommage des classes avec le suffixe «Info», par exemple: «SomeClass» et «SomeClassInfo»?

20

Je travaille dans un projet qui traite des périphériques physiques, et je ne sais pas comment nommer correctement certaines classes de ce projet.

Étant donné que les appareils réels (capteurs et récepteurs) sont une chose et que leur représentation dans le logiciel en est une autre, je pense à nommer certaines classes avec le modèle de nom de suffixe "Info".

Par exemple, alors que a Sensorserait une classe pour représenter le capteur réel (lorsqu'il est réellement connecté à un appareil en fonctionnement), SensorInfoserait utilisé pour représenter uniquement les caractéristiques d'un tel capteur. Par exemple, lors de l'enregistrement du fichier, je sérialiserais un SensorInfodans l'en-tête du fichier, au lieu de sérialiser un Sensor, ce qui n'aurait même pas de sens.

Mais maintenant, je suis confus, car il y a un point médian sur le cycle de vie des objets où je ne peux pas décider si je dois utiliser l'un ou l'autre, ou comment les obtenir les uns des autres, ou même si les deux variantes doivent en fait être réduites à une seule classe.

De plus, l'exemple de Employeeclasse trop commun n'est évidemment qu'une représentation de la personne réelle, mais personne ne suggérerait de nommer la classe à la EmployeeInfoplace, pour autant que je sache.

Le langage avec lequel je travaille est .NET, et ce modèle de dénomination semble être commun dans tout le framework, par exemple avec ces classes:

  • Directoryet DirectoryInfocours;
  • Fileet FileInfocours;
  • ConnectionInfoclasse (sans Connectionclasse correspondante );
  • DeviceInfoclasse (sans Deviceclasse correspondante );

Ma question est donc la suivante: existe-t-il une justification commune à l'utilisation de ce modèle de dénomination? Y a-t-il des cas où il est logique d'avoir des paires de noms ( Thinget ThingInfo) et d'autres cas où il ne devrait exister que la ThingInfoclasse, ou la Thingclasse, sans son homologue?

heltonbiker
la source
5
Ben Aaronson a bien compris dans sa réponse ci-dessous; le Infosuffixe distingue une staticclasse contenant des méthodes utilitaires de son homologue avec état. Ce n'est pas une "meilleure pratique" en tant que telle; c'est juste un moyen que l'équipe .NET a trouvé pour résoudre un problème particulier. Ils auraient pu tout aussi facilement trouver FileUtilityet File, mais File.DoSomething()et FileInfo.FileNamesemblent mieux lire.
Robert Harvey
1
Il convient de noter qu'il s'agit d'un modèle courant, mais avec autant de conventions de dénomination différentes qu'il y a de langues et d'API. En Java, par exemple, si vous avez une classe avec état Foo, vous pouvez avoir une classe utilitaire non instanciable Foos. Lorsqu'il s'agit de nommer, l'important est la cohérence au sein de l'API, et idéalement entre les API de la plate-forme.
Kevin Krumwiede
1
Vraiment? "Personne ne suggérerait de nommer la classe EmployeeInfo" ?? PERSONNE? Cela semble un peu fort pour quelque chose de moins évident.
ThePopMachine
@ThePopMachine Je suis d'accord que le mot "personne" pourrait être trop dur dans ce cas, mais Employeedes dizaines sont trouvés par des dizaines, en ligne ou dans des livres classiques, alors que je ne les ai pas EmployeeInfoencore vus (peut-être parce qu'un employé est un être vivant, pas une construction technique comme une connexion ou un fichier). Mais, d'accord, si la classe EmployeeInfodevait être proposée dans un projet, je pense qu'elle pourrait avoir son utilité.
heltonbiker

Réponses:

21

Je pense que "info" est un terme impropre. Les objets ont un état et des actions: "info" est juste un autre nom pour "état" qui est déjà intégré dans la POO.

Qu'essayez-vous vraiment de modéliser ici? Vous avez besoin d'un objet qui représente le matériel dans le logiciel afin qu'un autre code puisse l'utiliser.

C'est facile à dire, mais comme vous l'avez découvert, il y a plus que cela. "Représenter le matériel" est étonnamment large. Un objet qui fait cela a plusieurs préoccupations:

  • Communication de bas niveau avec l'appareil, qu'il s'agisse de l'interface USB, d'un port série, TCP / IP ou d'une connexion propriétaire.
  • Gestion de l'état. L'appareil est-il allumé? Prêt à parler à un logiciel? Occupé?
  • Gestion des événements. L'appareil a produit des données: nous devons maintenant générer des événements à transmettre à d'autres classes intéressées.

Certains périphériques tels que les capteurs auront moins de soucis qu'un périphérique multifonction imprimante / scanner / fax. Un capteur produit probablement un flux binaire, tandis qu'un appareil complexe peut avoir des protocoles et des interactions complexes.

Quoi qu'il en soit, revenons à votre question spécifique, il existe plusieurs façons de le faire en fonction de vos besoins spécifiques ainsi que de la complexité de l'interaction matérielle.

Voici un exemple de la façon dont je voudrais concevoir la hiérarchie des classes pour un capteur de température:

  • ITemperatureSource: interface qui représente tout ce qui peut produire des données de température: un capteur, peut même être un wrapper de fichier ou des données codées en dur (pensez: tests simulés).

  • Acme4680Sensor: capteur ACME modèle 4680 (idéal pour détecter quand le Roadrunner est à proximité). Cela peut implémenter plusieurs interfaces: ce capteur détecte peut-être à la fois la température et l'humidité. Cet objet contient un état au niveau du programme tel que "le capteur est-il connecté?" et "quelle a été la dernière lecture?"

  • Acme4680SensorComm: utilisé uniquement pour communiquer avec le périphérique physique. Il ne maintient pas beaucoup d'état. Il est utilisé pour envoyer et recevoir des messages. Il a une méthode C # pour chacun des messages que le matériel comprend.

  • HardwareManager: utilisé pour obtenir des appareils. Il s'agit essentiellement d'une fabrique qui met en cache des instances: il ne doit y avoir qu'une seule instance d'un objet périphérique pour chaque périphérique matériel. Il doit être suffisamment intelligent pour savoir que si le thread A demande le capteur de température ACME et le thread B demande le capteur d'humidité ACME, ce sont en fait le même objet et doivent être renvoyés aux deux threads.


Au niveau supérieur, vous disposerez d'interfaces pour chaque type de matériel. Ils décrivent les actions que votre code C # prendrait sur les périphériques, en utilisant des types de données C # (pas par exemple des tableaux d'octets que le pilote de périphérique brut pourrait utiliser).

Au même niveau, vous avez une classe d'énumération avec une instance pour chaque type de matériel. Le capteur de température peut être d'un type, le capteur d'humidité un autre.

Un niveau en dessous se trouve les classes réelles qui implémentent ces interfaces: elles représentent un périphérique similaire au capteur Acme4680 que j'ai décrit ci-dessus. Toute classe particulière peut implémenter plusieurs interfaces si le périphérique peut exécuter plusieurs fonctions.

Chaque classe de périphérique possède sa propre classe Comm (communication) privée qui gère la tâche de bas niveau de communication avec le matériel.

En dehors du module matériel, la seule couche visible est les interfaces / enum plus le HardwareManager. La classe HardwareManager est l'abstraction d'usine qui gère l'instanciation des classes de périphériques, les instances de mise en cache (vous ne voulez vraiment pas que deux classes de périphériques parlent au même périphérique matériel), etc. Une classe qui a besoin d'un type particulier de capteur demande au HardwareManager d'obtenir l'appareil pour l'énumération particulière, qu'il détermine ensuite s'il est déjà instancié, sinon comment le créer et l'initialiser, etc.

Le but ici est de dissocier la logique métier de la logique matérielle de bas niveau. Lorsque vous écrivez du code qui imprime des données de capteur à l'écran, ce code ne devrait pas se soucier du type de capteur que vous avez si et seulement si ce découplage est en place et centré sur ces interfaces matérielles.


Exemple de diagramme de classes UML montrant la conception décrite dans cette réponse

Remarque: il existe des associations entre le HardwareManager et chaque classe de périphérique que je n'ai pas dessiné car le diagramme se serait transformé en soupe aux flèches.


la source
Réponse très intéressante. Je demanderais une modification rapide: laissez plus explicite quel est l'héritage / confinement / collaboration entre les quatre classes que vous avez mentionnées. Merci beaucoup!
heltonbiker
J'ai ajouté un diagramme de classe UML. est-ce que cela aide?
2
Je dirais que c'est une réponse qui tue , et cela va non seulement m'aider beaucoup, mais je pense que cela pourrait aider beaucoup plus de gens à l'avenir. De plus, l'exemple de hiérarchie de classes que vous fournissez correspond remarquablement bien à nos domaines de problèmes et de solutions. Encore merci beaucoup!
heltonbiker
Quand j'ai lu la question, j'ai réalisé qu'il y avait plus de choses, alors je suis allé un peu exagéré et j'ai partagé tout le design. C'est plus qu'un simple problème de dénomination car les noms sont indicatifs de la conception. J'ai travaillé avec des systèmes construits de cette façon et ils fonctionnaient assez bien.
11

Il peut être un peu difficile de trouver une seule convention unificatrice ici parce que ces classes sont réparties sur un certain nombre d'espaces de noms ( ConnectionInfosemble être dans CrystalDecisionset DeviceInfodans System.Reporting.WebForms).

En regardant ces exemples, cependant, il semble y avoir deux utilisations distinctes du suffixe:

  • Distinguer une classe fournissant des méthodes statiques d'une classe fournissant des méthodes d'instance. C'est le cas des System.IOclasses, comme le souligne leur description:

    Annuaire :

    Expose des méthodes statiques pour créer, déplacer et énumérer via des répertoires et sous-répertoires. Cette classe ne peut pas être héritée.

    DirectoryInfo :

    Expose des méthodes d'instance pour créer, déplacer et énumérer via des répertoires et sous-répertoires. Cette classe ne peut pas être héritée.

    Infosemble être un choix un peu étrange ici, mais cela rend la différence relativement claire: une Directoryclasse peut raisonnablement représenter un répertoire particulier ou fournir des méthodes d'assistance générales liées au répertoire sans détenir aucun état, alors qu'elle DirectoryInfone peut vraiment être que la première.

  • Soulignant que la classe ne contient que des informations et ne fournit pas de comportement qui pourrait raisonnablement être attendu du nom non suffixé .

    Je pense que la dernière partie de cette phrase pourrait être la pièce du puzzle qui distingue, disons, ConnectionInfode EmployeeInfo. Si j'avais une classe appelée Connection, je m'attendrais raisonnablement à ce qu'elle me fournisse réellement les fonctionnalités d'une connexion - je chercherais des méthodes comme void Open(), etc. Cependant, personne sensé ne s'attendrait à ce qu'une Employeeclasse puisse réellement faire ce que fait un vrai Employee, ou chercher des méthodes comme void DoPaperwork()ou bool TryDiscreetlyBrowseFacebook().

Ben Aaronson
la source
1
Merci beaucoup pour votre réponse! Je crois que la première puce de votre réponse décrit définitivement ce qui se passe dans les classes .NET, mais la seconde a plus de valeur (et soit dit en passant est différente), car elle révèle mieux l'abstraction prévue: une chose à utiliser contre une chose à décrire. Il était difficile de choisir la réponse à accepter.
heltonbiker
4

En général, un Infoobjet encapsule des informations sur l'état d'un objet à un moment donné . Si je demande au système de regarder un fichier et de me donner un FileInfoobjet associé à sa taille, je m'attendrais à ce que cet objet indique la taille du fichier au moment où la demande a été donnée (ou, pour être plus précis, la taille de le fichier à un moment donné entre l'appel et le retour). Si la taille du fichier change entre le moment où la demande revient et le moment où l' FileInfoobjet est examiné, je ne m'attendrais pas à ce qu'une telle modification se reflète dans l' FileInfoobjet.

Notez que ce comportement serait très différent de celui d'un Fileobjet. Si une demande d'ouverture d'un fichier disque en mode non exclusif donne un Fileobjet qui a une Sizepropriété, je m'attendrais à ce que la valeur renvoyée change ainsi lorsque la taille du fichier disque change, car l' Fileobjet ne représente pas simplement l'état de un fichier - il représente le fichier lui-même .

Dans de nombreux cas, les objets qui s'attachent à une ressource doivent être nettoyés lorsque leurs services ne sont plus nécessaires. Étant donné que les *Infoobjets ne sont pas attachés aux ressources, ils ne nécessitent pas de nettoyage. Par conséquent, dans les cas où un Infoobjet satisfera aux exigences d'un client, il peut être préférable que le code en utilise un plutôt que d'utiliser un objet qui représenterait la ressource sous-jacente, mais dont la connexion à cette ressource devrait être nettoyée.

supercat
la source
1
Ce n'est pas exactement faux, mais cela ne semble pas du tout expliquer très bien les modèles de dénomination de .NET, car la Fileclasse ne peut pas être instanciée et les FileInfoobjets se mettent à jour avec le système de fichiers sous-jacent.
Nathan Tuggy
@NathanTuggy Je suis d'accord que cela ne correspond pas bien à cette convention de dénomination dans .NET, mais en ce qui concerne mon problème actuel, je pense qu'il est très pertinent et pourrait induire une solution cohérente. J'ai voté pour cette réponse, mais il est difficile de n'en choisir qu'une seule pour accepter ...
heltonbiker
@NathanTuggy: Je ne recommanderais pas d'utiliser .NET comme modèle de cohérence. C'est un bon et utile Framework qui encapsule beaucoup de bonnes idées, mais quelques mauvaises idées sont également capturées dans le mix.
supercat
@supercat: Bien sûr; il semble juste étrange de répondre à une question sur "pourquoi .NET fait-il les choses de cette façon?" avec "ce serait une bonne idée de faire les choses% THIS_WAY%".
Nathan Tuggy
@NathanTuggy: Je ne me souvenais pas des détails exacts des classes en question, mais je pensais FileInfosimplement détenir des informations capturées statiquement. N'est-ce pas? De plus, je n'ai jamais eu l'occasion d'ouvrir des fichiers en mode non exclusif, mais cela devrait être possible, et je m'attendrais à ce qu'il existe une méthode qui signalera la taille actuelle d'un fichier, même si l'objet utilisé pour l'ouverture n'est pas appelé File(normalement je viens d' utiliser ReadAllBytes, WriteAllBytes, ReadAllText, WriteAllText, etc.).
supercat
2

Étant donné que les appareils réels (capteurs et récepteurs) sont une chose et que leur représentation dans le logiciel en est une autre, je pense à nommer certaines classes avec le modèle de nom de suffixe "Info".

Par exemple, alors que a Sensorserait une classe pour représenter le capteur réel (lorsqu'il est réellement connecté à un appareil en fonctionnement), SensorInfoserait utilisé pour représenter uniquement les caractéristiques d'un tel capteur. Par exemple, lors de l'enregistrement du fichier, je sérialiserais un SensorInfodans l'en-tête du fichier, au lieu de sérialiser un Sensor, ce qui n'aurait même pas de sens.

Je n'aime pas cette distinction. Tous les objets sont des «représentations [dans le logiciel]». Voilà ce que signifie le mot «objet».

Maintenant, il peut être judicieux de séparer les informations sur un périphérique du code réel qui s'interface avec le périphérique. Ainsi, par exemple, un Sensor a-a SensorInfo, qui contient la plupart des variables d'instance, ainsi que certaines méthodes qui ne nécessitent pas de matériel, tandis que la classe Sensor est chargée d'interagir réellement avec le capteur physique. Vous n'avez pas-a à Sensormoins que votre ordinateur ne dispose d'un capteur, mais vous pourriez en avoir un SensorInfo.

Le problème est que ce type de conception peut être généralisé à (presque) n'importe quelle classe. Donc, vous devez être prudent. Vous n'auriez évidemment pas de SensorInfoInfocours, par exemple. Et si vous avez une Sensorvariable, vous pourriez vous retrouver en train de violer la loi de Demeter en interagissant avec son SensorInfomembre. Bien sûr, rien de tout cela n'est fatal, mais la conception d'API n'est pas réservée aux auteurs de bibliothèques. Si vous gardez votre propre API propre et simple, votre code sera plus facile à gérer.

Les ressources du système de fichiers comme les répertoires, à mon avis, sont très proches de ce bord. Il existe certaines situations dans lesquelles vous souhaitez décrire un répertoire qui n'est pas accessible localement, c'est vrai, mais le développeur moyen n'est probablement pas dans l'une de ces situations. À mon avis, compliquer la structure des classes est inutile. Comparez l'approche de Python dans pathlib: Il existe une seule classe qui "est probablement ce dont vous avez besoin" et diverses classes auxiliaires que la plupart des développeurs peuvent ignorer en toute sécurité. Si vous en avez vraiment besoin, cependant, ils fournissent en grande partie la même interface, juste avec des méthodes concrètes supprimées.

Kevin
la source
Merci pour votre réponse et bravo pour votre construction "have-a";) Votre réponse me rappelle l'inconfort causé à certains par le concept "DTO" (Data Transfer Object) - ou son frère l'Objet Paramètre - qui est mal vu par des fanatiques OO plus orthodoxes car il ne contient généralement pas de méthodes (après tout, c'est un objet de transfert de données ). En ce sens, et résumant également ce que d'autres ont dit, je pense que ce SensorInfoserait une sorte de DTO, et / ou aussi comme un "instantané", ou même une "spécification", représentant uniquement la partie données / état de l' Sensorobjet réel que "peut-être même pas là-bas".
heltonbiker
Comme pour la plupart des problèmes de conception, il n'y a pas de règle stricte. Dans l'exemple pathlib que j'ai fourni, PureWindowsPathagit un peu comme un objet Info, mais il a des méthodes pour faire des choses qui ne nécessitent pas de système Windows (par exemple, prendre un sous-répertoire, séparer une extension de fichier, etc.). C'est plus utile que de simplement fournir une structure glorifiée.
Kevin
1
Encore une fois, bravo pour la partie "structure glorifiée" :). Cela me rappelle une citation connexe du livre "Clean Code" d'oncle Bob, chapitre 6: "Les programmeurs matures savent que l'idée que tout est un objet est un mythe. Parfois, vous voulez vraiment des structures de données simples avec des procédures qui fonctionnent dessus."
heltonbiker
2

Je dirais que le contexte / domaine est important, car nous avons un code de logique métier de haut niveau et des modèles de bas niveau, des composants d'architecture, etc.

'Info', 'Data', 'Manager', 'Object', 'Class', 'Model', 'Controller' etc. peuvent être des suffixes malodorants, en particulier à un niveau inférieur, car chaque objet a des informations ou des données, donc cette information n'est pas nécessaire.

Les noms de classe du domaine commercial devraient être comme tous les intervenants en parlent, peu importe si cela semble bizarre ou n'est pas 100% correct.

Les bons suffixes pour les structures de données sont par exemple «Liste», «Carte» et pour les modèles de conseil «Décorateur», «Adaptateur» si vous pensez que cela est nécessaire.

Pour votre scénario de capteur, je ne m'attendrais pas SensorInfoà enregistrer ce qu'est votre capteur, mais SensorSpec. Infoimho est plus une information dérivée, comme FileInfoquelque chose comme la taille, vous ne sauvegardez pas ou le chemin de fichier qui est construit à partir du chemin et du nom de fichier, etc.

Un autre point:

Il n'y a que deux choses difficiles en informatique: l'invalidation du cache et les noms.

- Phil Karlton

Cela me rappelle toujours de penser à un nom juste pendant quelques secondes et si je n'en trouve pas, j'utilise des noms étranges et je les marque «TODO». Je peux toujours le changer plus tard car mon IDE fournit un support de refactoring. Ce n'est pas un slogan d'entreprise qui doit être bon, mais juste du code que nous pouvons changer chaque fois que nous le voulons. Garde cela à l'esprit.

Aitch
la source
1

ThingInfo peut être un excellent proxy en lecture seule pour la chose.

voir http://www.dofactory.com/net/proxy-design-pattern

Proxy: "Fournissez un substitut ou un espace réservé à un autre objet pour contrôler l'accès à celui-ci."

Typiquement, le ThingInfo aura des propriétés publiques sans setters. Ces classes et les méthodes de la classe sont sûres à utiliser et n'engageront aucune modification des données de sauvegarde, de l'objet ou de tout autre objet. Aucun changement d'état ou autre effet secondaire ne se produira. Ceux-ci peuvent être utilisés pour les rapports et les services Web ou partout où vous avez besoin d'informations sur l'objet mais souhaitez limiter l'accès à l'objet lui-même.

Utilisez le ThingInfo chaque fois que possible et limitez l'utilisation du Thing réel aux moments dont vous avez réellement besoin pour changer l'objet Thing. Cela rend la lecture et le débogage considérablement plus rapides lorsque vous vous habituez à utiliser ce modèle.

Shmoken
la source
Excellent. Plus tôt dans la journée, j'analysais mes besoins architecturaux, en ce qui concerne les classes que j'ai utilisées dans la question, et je suis arrivé à cette même conclusion. Dans mon cas, j'ai un Receiverqui reçoit des flux de données de nombreux Sensors. L'idée est la suivante: le récepteur doit abstraire les capteurs réels. Mais le problème est: j'ai besoin des informations du capteur pour pouvoir les écrire dans un en-tête de fichier. La solution: chacun IReceiveraura une liste de SensorInfo. Si j'envoie des commandes au récepteur qui impliquent de changer l'état du capteur, ces changements seront reflétés (via getter) sur le respectif SensorInfo.
heltonbiker
(suite ...) c'est-à-dire: je ne parle pas aux capteurs eux-mêmes, je parle uniquement au récepteur via des commandes de niveau supérieur, et le récepteur à son tour parle à chacun des capteurs. Mais j'ai finalement besoin d'informations provenant des capteurs, que j'obtiens de l'instance Receiver via sa List<SensorInfo>propriété en lecture seule.
heltonbiker
1

Jusqu'à présent, personne dans cette question ne semble avoir compris la véritable raison de cette convention de dénomination.

A DirectoryInfon'est pas le répertoire. Il s'agit d' un DTO contenant des données sur l'annuaire. Il peut y avoir de nombreuses instances décrivant le même répertoire. Ce n'est pas une entité. Il s'agit d'un objet de valeur à jeter. A DirectoryInfone représente pas le répertoire réel. Vous pouvez également le considérer comme un handle ou un contrôleur pour un répertoire.

Comparez cela avec une classe nommée Employee. Il peut s'agir d'un objet entité ORM et il s'agit de l'objet unique décrivant cet employé. S'il s'agissait d'un objet de valeur sans identité, il devrait être appelé EmployeeInfo. Un Employeereprésente effectivement l'employé réel. Une classe DTO de type valeur appelée EmployeeInfone représenterait clairement pas l'employé, mais plutôt la décrit ou stocke des données à son sujet.

Il y a en fait un exemple dans la BCL où les deux classes existent: A ServiceControllerest une classe qui décrit un service Windows. Il peut y avoir n'importe quel nombre de ces contrôleurs pour chaque service. A ServiceBase(ou une classe dérivée) est le service réel et il n'a pas de sens conceptuel d'en avoir plusieurs instances par service distinct.

usr
la source
Cela ressemble au Proxymodèle mentionné par @Shmoken, n'est-ce pas?
heltonbiker
@heltonbiker, il ne doit pas nécessairement être un proxy. Il pourrait s'agir d'un objet qui n'est en aucun cas lié à ce qu'il décrit. Par exemple, vous pouvez obtenir un EmployeeInfoservice Web. Ce n'est pas un proxy. Autre exemple: Un AddressInfone même pas avoir une chose qu'il peut proxy , car une adresse est pas une entité. C'est une valeur autonome.
usr
1
Je vois, ça a du sens!
heltonbiker