Ich arbeite in einem Projekt, das sich mit physischen Geräten befasst, und ich war verwirrt, wie ich einige Klassen in diesem Projekt richtig benennen soll.
Angesichts der Tatsache, dass die tatsächlichen Geräte (Sensoren und Empfänger) eine Sache sind und ihre Darstellung in der Software eine andere, denke ich darüber nach, einige Klassen mit dem Suffix-Namensmuster "Info" zu benennen.
Während beispielsweise a Sensor
eine Klasse ist, die den tatsächlichen Sensor darstellt (wenn er tatsächlich mit einem Arbeitsgerät verbunden ist), wird SensorInfo
sie verwendet, um nur die Eigenschaften eines solchen Sensors darzustellen. Zum Beispiel würde ich beim Speichern von Dateien a SensorInfo
in den Datei-Header serialisieren, anstatt a zu serialisieren Sensor
, was nicht einmal Sinn macht.
Aber jetzt bin ich verwirrt, weil es einen Mittelweg im Lebenszyklus von Objekten gibt, auf dem ich nicht entscheiden kann, ob ich die eine oder andere verwenden oder wie ich eine von einer anderen erhalten soll oder ob beide Varianten tatsächlich nur zu einer Klasse zusammengefasst werden sollen.
Außerdem ist die allzu verbreitete Beispielklasse Employee
offensichtlich nur eine Darstellung der realen Person, aber meines Wissens würde niemand vorschlagen, die Klasse EmployeeInfo
stattdessen zu benennen .
Die Sprache, mit der ich arbeite, ist .NET, und dieses Benennungsmuster scheint im gesamten Framework verbreitet zu sein, zum Beispiel mit den folgenden Klassen:
Directory
undDirectoryInfo
Klassen;File
undFileInfo
Klassen;ConnectionInfo
Klasse (ohne KorrespondenzklasseConnection
);DeviceInfo
Klasse (ohne KorrespondenzklasseDevice
);
Meine Frage lautet also: Gibt es eine allgemeine Begründung für die Verwendung dieses Benennungsmusters? Gibt es Fälle, in denen es sinnvoll ist, Namenspaare ( Thing
und ThingInfo
) zu haben, und andere Fälle, in denen nur die ThingInfo
Klasse oder die Thing
Klasse ohne ihr Gegenstück existieren sollte?
quelle
Info
Suffix unterscheidet einestatic
Klasse mit Dienstprogrammmethoden von ihrem zustandsbehafteten Gegenstück. Es ist keine "Best Practice" als solche; Es ist nur ein Weg, den das .NET-Team gefunden hat, um ein bestimmtes Problem zu lösen. Sie hätten genauso gut kommen mitFileUtility
undFile
aberFile.DoSomething()
undFileInfo.FileName
scheinen besser zu lesen.Foo
verfügen, verfügen Sie möglicherweise über eine nicht instanziierbare Utility-KlasseFoos
. Bei der Benennung kommt es auf die Konsistenz innerhalb der API und idealerweise über alle APIs auf der Plattform hinweg an.Employee
Beispiele finden sich in Dutzenden, entweder online oder in klassischen Büchern, die ich noch nicht gesehenEmployeeInfo
habe (vielleicht, weil ein Mitarbeiter ein Lebewesen ist, nicht ein technisches Konstrukt wie eine Verbindung oder eine Datei). Aber einverstanden, wenn die KlasseEmployeeInfo
in einem Projekt vorgeschlagen würde, könnte sie meiner Meinung nach ihren Nutzen haben.Antworten:
Ich denke, "info" ist eine falsche Bezeichnung. Objekte haben Status und Aktionen: "info" ist nur ein anderer Name für "state", der bereits in OOP eingebunden ist.
Was versuchst du hier wirklich zu modellieren? Sie benötigen ein Objekt, das die Hardware in der Software darstellt, damit es von anderem Code verwendet werden kann.
Das ist leicht zu sagen, aber wie Sie herausgefunden haben, steckt noch mehr dahinter. "Darstellung von Hardware" ist überraschend breit. Ein Objekt, das dies tut, hat mehrere Bedenken:
Bestimmte Geräte wie Sensoren haben weniger Probleme als beispielsweise ein Drucker / Scanner / Fax-Multifunktionsgerät. Ein Sensor erzeugt wahrscheinlich nur einen Bitstrom, während ein komplexes Gerät komplexe Protokolle und Interaktionen aufweisen kann.
Wie auch immer, zurück zu Ihrer spezifischen Frage, es gibt verschiedene Möglichkeiten, dies abhängig von Ihren spezifischen Anforderungen sowie der Komplexität der Hardware-Interaktion zu tun.
Hier ist ein Beispiel, wie ich die Klassenhierarchie für einen Temperatursensor entwerfen würde:
ITemperatureSource: Schnittstelle, die alles darstellt, was Temperaturdaten erzeugen kann: ein Sensor, kann sogar ein File-Wrapper oder fest codierte Daten sein (man denke an Scheintests).
Acme4680Sensor: Sensor ACME Modell 4680 (ideal zum Erkennen, wenn der Roadrunner in der Nähe ist). Dies kann mehrere Schnittstellen implementieren: Vielleicht erkennt dieser Sensor sowohl Temperatur als auch Luftfeuchtigkeit. Dieses Objekt enthält einen Status auf Programmebene, z. B. "Ist der Sensor angeschlossen?". und "was war die letzte Lesung?"
Acme4680SensorComm: Wird ausschließlich zur Kommunikation mit dem physischen Gerät verwendet. Es behält nicht viel Zustand bei. Es wird zum Senden und Empfangen von Nachrichten verwendet. Es gibt eine C # -Methode für jede Nachricht, die die Hardware versteht.
HardwareManager: Dient zum Abrufen von Geräten. Dies ist im Wesentlichen eine Factory, die Instanzen zwischenspeichert: Für jedes Hardwaregerät sollte nur eine Instanz eines Geräteobjekts vorhanden sein. Es muss klug genug sein zu wissen, dass, wenn Thread A den ACME-Temperatursensor und Thread B den ACME-Feuchtigkeitssensor anfordert, diese tatsächlich dasselbe Objekt sind und an beide Threads zurückgegeben werden sollten.
Auf der obersten Ebene haben Sie Schnittstellen für jeden Hardwaretyp. Sie beschreiben Aktionen, die Ihr C # -Code auf den Geräten ausführen würde, und verwenden dabei C # -Datentypen (nicht z. B. Byte-Arrays, die der Raw-Gerätetreiber möglicherweise verwendet).
Auf derselben Ebene haben Sie eine Aufzählungsklasse mit einer Instanz für jeden Hardwaretyp. Der Temperatursensor kann ein Typ sein, der Feuchtigkeitssensor ein anderer.
Eine Ebene darunter befinden sich die tatsächlichen Klassen, die diese Schnittstellen implementieren: Sie stellen ein Gerät dar, das dem oben beschriebenen Acme4680Sensor I ähnelt. Jede bestimmte Klasse kann mehrere Schnittstellen implementieren, wenn das Gerät mehrere Funktionen ausführen kann.
Jede Geräteklasse verfügt über eine eigene private Kommunikationsklasse, die die einfache Aufgabe der Kommunikation mit der Hardware übernimmt.
Außerhalb des Hardwaremoduls ist nur die Ebene interfaces / enum sowie der HardwareManager sichtbar. Die HardwareManager-Klasse ist die Factory-Abstraktion, die die Instanziierung von Geräteklassen, das Zwischenspeichern von Instanzen (Sie möchten wirklich nicht, dass zwei Geräteklassen mit demselben Hardwaregerät kommunizieren) usw. verwaltet. Eine Klasse, die einen bestimmten Sensortyp benötigt, fordert den HardwareManager auf, diesen abzurufen das Gerät für die bestimmte Aufzählung, das dann ermittelt, ob es bereits instanziiert ist, ob es nicht erstellt und initialisiert werden soll usw.
Ziel ist es, die Geschäftslogik von der Hardware-Logik auf niedriger Ebene zu entkoppeln . Wenn Sie Code schreiben, der Sensordaten auf den Bildschirm druckt, sollte es diesen Code nicht interessieren, welchen Sensortyp Sie genau dann haben, wenn diese Entkopplung vorhanden ist, die sich auf diese Hardwareschnittstellen konzentriert.
Hinweis: Es gibt Verknüpfungen zwischen dem HardwareManager und jeder Geräteklasse, die ich nicht gezeichnet habe, da das Diagramm zu einer Pfeilsuppe geworden wäre.
quelle
Es kann ein wenig schwierig sein, hier eine einzige vereinheitlichende Konvention zu finden, da diese Klassen über eine Reihe von Namespaces verteilt sind (
ConnectionInfo
scheint inCrystalDecisions
undDeviceInfo
in zu seinSystem.Reporting.WebForms
).Wenn man sich diese Beispiele ansieht, scheint es jedoch zwei unterschiedliche Verwendungen des Suffixes zu geben:
Unterscheiden einer Klasse, die statische Methoden bereitstellt, in eine Klasse, die Instanzmethoden bereitstellt. Dies ist der Fall für die
System.IO
Klassen, wie durch ihre Beschreibungen unterstrichen:Verzeichnis :
DirectoryInfo :
Info
Dies scheint eine etwas seltsame Wahl zu sein, macht jedoch den Unterschied relativ deutlich: EineDirectory
Klasse könnte vernünftigerweise entweder ein bestimmtes Verzeichnis darstellen oder allgemeine verzeichnisbezogene Hilfsmethoden bereitstellen, ohne einen Status zu haben, währendDirectoryInfo
dies nur die erstere sein könnte.Hervorheben, dass die Klasse nur Informationen enthält und kein Verhalten liefert , das von dem Namen ohne Suffix vernünftigerweise erwartet werden kann .
Ich denke , dass der letzte Teil dieses Satzes könnte das Stück des Puzzles sein , dass unterscheidet, sagen,
ConnectionInfo
ausEmployeeInfo
. Wenn ich eine Klasse mit dem Namen hätteConnection
, würde ich davon ausgehen, dass sie mir tatsächlich die Funktionalität bietet, die eine Verbindung bietet - ich würde nach Methoden wievoid Open()
usw. suchen . Allerdings würde niemand, der bei Verstand ist, damit rechnen, dass eineEmployee
Klasse dies tatsächlich könnte Mach was ein RealEmployee
macht, oder suche nach Methoden wievoid DoPaperwork()
oderbool TryDiscreetlyBrowseFacebook()
.quelle
Im Allgemeinen
Info
kapselt ein Objekt Informationen über den Status eines Objekts zu einem bestimmten Zeitpunkt . Wenn ich das System auffordere, eine Datei anzusehen und mir einFileInfo
Objekt mit der Größe zuzuordnen, würde ich erwarten, dass dieses Objekt die Größe der Datei zum Zeitpunkt der Anforderung angibt (oder genauer gesagt die Größe von die Datei irgendwann zwischen dem Zeitpunkt des Anrufs und der Rückkehr). Wenn sich die Größe der Datei zwischen der Rückkehr der Anforderung und derFileInfo
Untersuchung des Objekts ändert , würde ich nicht erwarten, dass sich eine solche Änderung imFileInfo
Objekt niederschlägt.Beachten Sie, dass sich dieses Verhalten stark von dem eines
File
Objekts unterscheidet. Wenn eine Anforderung zum Öffnen einer Festplattendatei im nicht exklusiven Modus einFile
Objekt mit einerSize
Eigenschaft ergibt , würde ich erwarten, dass sich der zurückgegebene Wert ändert, wenn sich die Größe der Festplattendatei ändert, da dasFile
Objekt nicht nur den Status von darstellt eine Datei - es repräsentiert die Datei selbst .In vielen Fällen müssen Objekte, die an eine Ressource gebunden sind, bereinigt werden, wenn ihre Dienste nicht mehr benötigt werden. Da
*Info
Objekte nicht an Ressourcen gebunden sind, müssen sie nicht bereinigt werden. Infolgedessen ist es in Fällen, in denen einInfo
Objekt die Anforderungen eines Kunden erfüllt, möglicherweise besser, einen Code zu verwenden, als ein Objekt zu verwenden, das die zugrunde liegende Ressource darstellt, dessen Verbindung zu dieser Ressource jedoch bereinigt werden müsste.quelle
File
Klasse nicht instanziiert werden kann undFileInfo
Objekte tun Updates mit dem zugrunde liegenden Dateisystem.FileInfo
nur statisch erfasste Informationen. Nicht wahr Außerdem hatte ich noch nie Gelegenheit, Dateien im nicht exklusiven Modus zu öffnen, aber es sollte möglich sein, und ich würde erwarten, dass es eine Methode gibt, die die aktuelle Größe einer Datei angibt, auch wenn das zum Öffnen verwendete Objekt nicht vorhanden ist genanntFile
( in der Regel nur verwende ichReadAllBytes
,WriteAllBytes
,ReadAllText
,WriteAllText
, etc.).Ich mag diese Unterscheidung nicht. Alle Objekte sind "Darstellungen in Software". Das ist, was das Wort "Objekt" bedeutet.
Nun ist es möglicherweise sinnvoll, Informationen zu einem Peripheriegerät vom eigentlichen Code zu trennen, der mit dem Peripheriegerät verbunden ist. So verfügt beispielsweise ein Sensor über eine SensorInfo, die die meisten Instanzvariablen sowie einige Methoden enthält, für die keine Hardware erforderlich ist, während die Sensorklasse für die tatsächliche Interaktion mit dem physischen Sensor verantwortlich ist. Sie haben keine, es
Sensor
sei denn, Ihr Computer hat einen Sensor, aber Sie könnten plausibel eine habenSensorInfo
.Das Problem ist, dass diese Art von Design auf (fast) jede Klasse verallgemeinert werden kann. Also musst du vorsichtig sein. Sie würden offensichtlich keine
SensorInfoInfo
Klasse haben, zum Beispiel. Und wenn Sie eineSensor
Variable haben, verletzen Sie möglicherweise das Gesetz von Demeter, indem Sie mit ihremSensorInfo
Mitglied interagieren . Nichts davon ist natürlich fatal, aber API-Design ist nicht nur für Bibliotheksautoren. Wenn Sie Ihre eigene API sauber und einfach halten, kann der Code besser verwaltet werden.Dateisystemressourcen wie Verzeichnisse sind meiner Meinung nach sehr nahe an diesem Rand. Es gibt Situationen, in denen Sie ein Verzeichnis beschreiben möchten, auf das lokal nicht zugegriffen werden kann, aber der durchschnittliche Entwickler befindet sich wahrscheinlich nicht in einer dieser Situationen. Es ist meiner Meinung nach wenig hilfreich, die Klassenstruktur auf diese Weise zu komplizieren. Der Ansatz von Contrast Python in
pathlib
: Es gibt eine einzige Klasse, die "mit größter Wahrscheinlichkeit das ist, was Sie brauchen", und verschiedene Hilfsklassen, die die meisten Entwickler ignorieren können. Wenn Sie sie jedoch wirklich brauchen, bieten sie im Wesentlichen die gleiche Oberfläche, nur ohne konkrete Methoden.quelle
SensorInfo
dies eine Art DTO und / oder auch eine "Momentaufnahme" oder sogar eine "Spezifikation" wäre, die nur den Daten- / Zustandsteil des tatsächlichenSensor
Objekts darstellt das "könnte nicht einmal da sein".PureWindowsPath
ein bisschen wie ein Info-Objekt, verfügt jedoch über Methoden, um Dinge zu erledigen, die kein Windows-System erfordern (z. B. ein Unterverzeichnis zu entfernen, eine Dateierweiterung abzuspalten usw.). Dies ist hilfreicher als nur eine verherrlichte Struktur bereitzustellen.Ich würde sagen, dass Kontext / Domäne von Bedeutung sind, da wir über hochrangigen Geschäftslogikcode und niedrigrangige Modelle, Architekturkomponenten usw. verfügen.
"Info", "Daten", "Manager", "Objekt", "Klasse", "Modell", "Controller" usw. können stinkende Suffixe sein, insbesondere auf einer niedrigeren Ebene, da jedes Objekt über einige Informationen oder Daten verfügt Diese Informationen sind nicht erforderlich.
Klassennamen der Geschäftsdomäne sollten so lauten, als ob alle Stakeholder darüber sprechen, egal ob sie komisch klingen oder nicht zu 100% die richtige Sprache sind.
Gute Suffixe für Datenstrukturen sind zB 'List', 'Map' und für Andeutungsmuster 'Decorator', 'Adapter', wenn Sie dies für notwendig halten.
Zu deinem Sensorszenario würde ich aber nicht erwarten
SensorInfo
zu retten was dein Sensor istSensorSpec
.Info
imho ist eher eine abgeleitete Information, wieFileInfo
etwa die Größe, die Sie nicht speichern oder der Dateipfad, der aus dem Pfad und dem Dateinamen aufgebaut ist, usw.Ein weiterer Punkt:
Das erinnert mich immer daran, nur ein paar Sekunden über einen Namen nachzudenken, und wenn ich keinen gefunden habe, benutze ich seltsame Namen und markiere sie mit 'TODO'-Zeichen. Ich kann es später jederzeit ändern, da meine IDE Refactoring-Unterstützung bietet. Es ist kein Firmenslogan, der gut sein muss, sondern nur ein Code, den wir jedes Mal ändern können, wenn wir wollen. Behalt das im Kopf.
quelle
ThingInfo kann als hervorragender schreibgeschützter Proxy für die Sache dienen.
Siehe http://www.dofactory.com/net/proxy-design-pattern
Proxy: "Geben Sie einen Ersatz oder Platzhalter für ein anderes Objekt an, um den Zugriff darauf zu steuern."
Normalerweise hat ThingInfo öffentliche Eigenschaften ohne Setter. Diese Klassen und die Methoden für die Klasse sind sicher zu verwenden und übernehmen keine Änderungen an den Sicherungsdaten, dem Objekt oder anderen Objekten. Es treten keine Zustandsänderungen oder andere Nebenwirkungen auf. Diese können für Berichts- und Webdienste oder überall dort verwendet werden, wo Sie Informationen zum Objekt benötigen, aber den Zugriff auf das eigentliche Objekt beschränken möchten.
Verwenden Sie die ThingInfo, wann immer dies möglich ist, und beschränken Sie die Verwendung der tatsächlichen Thing auf die Zeiten, zu denen Sie das Thing-Objekt tatsächlich ändern müssen. Es beschleunigt das Lesen und Debuggen erheblich, wenn Sie sich an die Verwendung dieses Musters gewöhnen.
quelle
Receiver
der Datenströme von vielenSensor
s empfängt . Die Idee ist: Der Empfänger sollte die tatsächlichen Sensoren abstrahieren. Aber das Problem ist: Ich brauche den Sensor info , so dass ich sie bis zu einem gewissen Dateiheader schreiben kann. Die Lösung: JederIReceiver
hat eine Liste vonSensorInfo
. Wenn ich Befehle an den Empfänger sende, die eine Änderung des Sensorzustands implizieren, werden diese Änderungen (über den Getter) auf dem jeweiligen Sensor wiedergegebenSensorInfo
.List<SensorInfo>
readonly-Eigenschaft erhalte.Bisher scheint niemand in dieser Frage den wahren Grund für diese Namenskonvention aufgegriffen zu haben.
A
DirectoryInfo
ist nicht das Verzeichnis. Es ist ein DTO mit Daten über das Verzeichnis. Es kann viele solcher Instanzen geben, die dasselbe Verzeichnis beschreiben. Es ist keine Einheit. Es ist ein Wegwerfwertobjekt. ADirectoryInfo
repräsentiert nicht das aktuelle Verzeichnis. Sie können es sich auch als Handle oder Controller für ein Verzeichnis vorstellen.Vergleichen Sie dies mit einer Klasse namens
Employee
. Dies kann ein ORM-Entitätsobjekt sein und es ist das einzige Objekt, das diesen Mitarbeiter beschreibt. Wenn es ein Wertobjekt ohne Identität war, sollte es aufgerufen werdenEmployeeInfo
. EinEmployee
steht in der Tat für den tatsächlichen Mitarbeiter. Eine aufgerufene wertmäßige DTO-KlasseEmployeeInfo
würde den Mitarbeiter eindeutig nicht darstellen, sondern ihn beschreiben oder Daten darüber speichern.In der BCL gibt es tatsächlich ein Beispiel, in dem beide Klassen vorhanden sind: A
ServiceController
ist eine Klasse, die einen Windows-Dienst beschreibt. Für jeden Dienst kann es eine beliebige Anzahl solcher Controller geben. EineServiceBase
(oder eine abgeleitete) Klasse ist der eigentliche Dienst und es ist konzeptionell nicht sinnvoll, mehrere Instanzen davon pro Dienst zu haben.quelle
Proxy
von @Shmoken erwähnte Muster, nicht wahr?EmployeeInfo
von einem Webdienst erhalten. Das ist kein Proxy. Ein weiteres Beispiel: EinAddressInfo
nicht einmal haben , eine Sache kann es Proxy , weil eine Adresse keine Einheit ist. Es ist ein eigenständiger Wert.