Qual è l'idea alla base delle classi di denominazione con il suffisso "Info", ad esempio: "SomeClass" e "SomeClassInfo"?


20

Sto lavorando a un progetto che si occupa di dispositivi fisici e sono stato confuso su come nominare correttamente alcune classi in questo progetto.

Considerando che i dispositivi effettivi (sensori e ricevitori) sono una cosa, e la loro rappresentazione nel software è un'altra, sto pensando di nominare alcune classi con il modello di nome del suffisso "Info".

Ad esempio, mentre a Sensorsarebbe una classe per rappresentare il sensore effettivo (quando è effettivamente collegato a un dispositivo funzionante), SensorInfoverrebbe utilizzato per rappresentare solo le caratteristiche di tale sensore. Ad esempio, dopo il salvataggio del file, vorrei serializzare un SensorInfonell'intestazione del file, anziché serializzare un Sensor, che non avrebbe nemmeno senso.

Ma ora sono confuso, perché c'è un punto centrale nel ciclo di vita degli oggetti in cui non posso decidere se dovrei usare l'uno o l'altro, o come ottenerne uno l'uno dall'altro, o anche se entrambe le varianti debbano effettivamente essere compresse in una sola classe.

Inoltre, la Employeeclasse di esempio fin troppo comune è ovviamente solo una rappresentazione della persona reale, ma nessuno suggerirebbe invece di nominare la classe EmployeeInfo, per quanto ne so.

Il linguaggio con cui sto lavorando è .NET e questo modello di denominazione sembra essere comune in tutto il framework, ad esempio con queste classi:

  • Directorye DirectoryInfoclassi;
  • Filee FileInfoclassi;
  • ConnectionInfoclasse (senza Connectionclasse corrispondente );
  • DeviceInfoclasse (senza Deviceclasse corrispondente );

Quindi la mia domanda è: c'è una logica comune sull'uso di questo modello di denominazione? Ci sono casi in cui ha senso avere coppie di nomi ( Thinge ThingInfo) e altri casi in cui dovrebbe esistere solo la ThingInfoclasse, o la Thingclasse, senza la sua controparte?


5
Ben Aaronson ha capito bene nella sua risposta di seguito; il Infosuffisso distingue una staticclasse contenente metodi di utilità dalla sua controparte stateful. Non è una "best practice" in quanto tale; è solo un modo in cui il team di .NET ha ideato per risolvere un problema particolare. Avrebbero potuto escogitare altrettanto facilmente FileUtilitye File, ma File.DoSomething()e FileInfo.FileNamesembrano leggere meglio.
Robert Harvey,

1
Vale la pena notare che questo è un modello comune, ma con tante convenzioni di denominazione diverse quante sono le lingue e le API. In Java, ad esempio, se si dispone di una classe stateful Foo, è possibile che sia presente una classe di utilità non istantanea Foos. Quando si tratta di nominare, l'importante è la coerenza all'interno dell'API e idealmente tra le API sulla piattaforma.
Kevin Krumwiede,

1
Veramente? "Nessuno consiglierebbe di nominare la classe EmployeeInfo" ?? NESSUNO? Sembra un po 'forte per qualcosa di non così ovvio.
ThePopMachine

@ThePopMachine Concordo sul fatto che la parola "nessuno" potrebbe essere troppo difficile in questo caso, ma Employeeesempi sono stati trovati dalle dozzine, sia online che nei libri classici, mentre non l'ho ancora visto EmployeeInfo(forse perché un dipendente è un essere vivente, non un costrutto tecnico come una connessione o un file). Ma, d'accordo, se la classe EmployeeInfodovesse essere proposta in un progetto, credo che potrebbe avere il suo uso.
heltonbiker

Risposte:


21

Penso che "info" sia un termine improprio. Gli oggetti hanno stato e azioni: "info" è solo un altro nome per "stato" che è già inserito in OOP.

Cosa stai davvero cercando di modellare qui? È necessario un oggetto che rappresenti l'hardware nel software in modo che altri codici possano utilizzarlo.

È facile da dire, ma come hai scoperto, c'è di più. "Rappresentare l'hardware" è sorprendentemente ampio. Un oggetto che lo fa presenta diversi problemi:

  • Comunicazione a basso livello del dispositivo, che si tratti di un'interfaccia USB, una porta seriale, TCP / IP o una connessione proprietaria.
  • Gestione dello stato. Il dispositivo è acceso? Pronto a parlare con il software? Occupato?
  • Gestione degli eventi. Il dispositivo ha prodotto dati: ora dobbiamo generare eventi da passare ad altre classi che sono interessate.

Alcuni dispositivi come i sensori avranno meno preoccupazioni rispetto a un dispositivo multifunzione stampante / scanner / fax. Un sensore probabilmente produce solo un flusso di bit, mentre un dispositivo complesso può avere protocolli e interazioni complessi.

Comunque, tornando alla tua domanda specifica, ci sono diversi modi per farlo a seconda delle tue esigenze specifiche e della complessità dell'interazione hardware.

Ecco un esempio di come progetterei la gerarchia di classi per un sensore di temperatura:

  • ITemperatureSource: interfaccia che rappresenta tutto ciò che può produrre dati di temperatura: un sensore, potrebbe persino essere un wrapper di file o dati hardcoded (pensate: test di simulazione).

  • Acme4680Sensore: sensore ACME modello 4680 (ottimo per rilevare quando il Roadrunner si trova nelle vicinanze). Questo può implementare più interfacce: forse questo sensore rileva sia la temperatura che l'umidità. Questo oggetto contiene uno stato a livello di programma come "il sensore è collegato?" e "qual è stata l'ultima lettura?"

  • Acme4680SensorComm: utilizzato esclusivamente per la comunicazione con il dispositivo fisico. Non mantiene molto lo stato. È usato per inviare e ricevere messaggi. Ha un metodo C # per ciascuno dei messaggi che l'hardware comprende.

  • HardwareManager: utilizzato per ottenere dispositivi. Questa è essenzialmente una fabbrica che memorizza nella cache le istanze: dovrebbe esserci solo un'istanza di un oggetto dispositivo per ogni dispositivo hardware. Deve essere abbastanza intelligente da sapere che se il thread A richiede il sensore di temperatura ACME e il thread B richiede il sensore di umidità ACME, questi sono in realtà lo stesso oggetto e devono essere restituiti ad entrambi i thread.


Al livello superiore avrai interfacce per ogni tipo di hardware. Descrivono le azioni che il codice C # intraprenderà sui dispositivi, utilizzando tipi di dati C # (non ad es. Array di byte che il driver di dispositivo non elaborato potrebbe utilizzare).

Allo stesso livello esiste una classe di enumerazione con un'istanza per ciascun tipo di hardware. Il sensore di temperatura potrebbe essere di un tipo, il sensore di umidità un altro.

Un livello al di sotto di questo sono le classi reali che implementano quelle interfacce: rappresentano un dispositivo simile al sensore Acme4680 che ho descritto sopra. Qualsiasi classe particolare può implementare più interfacce se il dispositivo può eseguire più funzioni.

Ogni classe di dispositivo ha una propria classe Comm (comunicazione) privata che gestisce l'attività di basso livello di comunicazione con l'hardware.

Al di fuori del modulo hardware, l'unico livello visibile sono le interfacce / enum e HardwareManager. La classe HardwareManager è l'astrazione di fabbrica che gestisce l'istanza di classi di dispositivi, istanze di cache (in realtà non si desidera che due classi di dispositivi parlino con lo stesso dispositivo hardware), ecc. Una classe che necessita di un particolare tipo di sensore chiede a HardwareManager di ottenere il dispositivo per il particolare enum, che poi capisce se è già istanziato, se non come crearlo e inizializzarlo, ecc.

L'obiettivo qui è di separare la logica aziendale dalla logica hardware di basso livello. Quando scrivi il codice che stampa i dati del sensore sullo schermo, quel codice non dovrebbe interessarti del tipo di sensore che hai se e solo se è presente questo disaccoppiamento centrato su quelle interfacce hardware.


Esempio di diagramma di classe UML che mostra il design descritto in questa risposta

Nota: ci sono associazioni tra HardwareManager e ogni classe di dispositivo che non ho disegnato perché il diagramma si sarebbe trasformato in una minestra di frecce.


Risposta molto interessante Vorrei una rapida modifica: lascia più esplicito qual è l'eredità / il contenimento / la collaborazione tra le quattro classi che hai citato. Grazie mille!
heltonbiker

Ho aggiunto un diagramma di classe UML. questo aiuta?

2
Direi che questa è una risposta killer , e non solo mi aiuterà molto, ma credo che potrebbe aiutare molte più persone in futuro. Inoltre, l'esempio della gerarchia di classi che fornisci mappa i nostri domini di problemi e soluzioni molto bene. Mille Grazie ancora!
heltonbiker

Quando ho letto la domanda mi sono reso conto che c'erano altre cose in corso, quindi sono andato un po 'eccessivo e ho condiviso l'intero progetto. Questo è più di un semplice problema di denominazione perché i nomi sono indicativi del design. Ho lavorato con sistemi costruiti in questo modo e hanno funzionato abbastanza bene.

11

Potrebbe essere un po 'difficile trovare un'unica convenzione unificante qui perché queste classi sono distribuite su un certo numero di spazi dei nomi, ( ConnectionInfosembra essere dentro CrystalDecisionse DeviceInfodentro System.Reporting.WebForms).

Guardando questi esempi, tuttavia, sembrano esserci due usi distinti del suffisso:

  • Distinguere una classe che fornisce metodi statici con una classe che fornisce metodi di istanza. Questo è il caso delle System.IOlezioni, come sottolineato dalle loro descrizioni:

    Directory :

    Espone metodi statici per la creazione, lo spostamento e l'enumerazione attraverso directory e sottodirectory. Questa classe non può essere ereditata.

    DirectoryInfo :

    Espone metodi di istanza per la creazione, lo spostamento e l'enumerazione attraverso directory e sottodirectory. Questa classe non può essere ereditata.

    Infosembra una scelta un po 'strana qui, ma rende la differenza relativamente chiara: una Directoryclasse potrebbe ragionevolmente rappresentare una directory particolare o fornire metodi di supporto generali relativi alla directory senza contenere alcuno stato, mentre DirectoryInfopotrebbe essere solo la prima.

  • Sottolineando che la classe contiene solo informazioni e non fornisce comportamenti che potrebbero ragionevolmente aspettarsi dal nome non suffisso .

    Penso che l'ultima parte di quella frase potrebbe essere il pezzo del puzzle che distingue, diciamo, ConnectionInfoda EmployeeInfo. Se avessi chiamato una classe Connection, mi sarei ragionevolmente aspettato che mi fornisse effettivamente la funzionalità di una connessione. Cercherei metodi come void Open(), ecc. Tuttavia, nessuno nella loro mente corretta si aspetterebbe che una Employeeclasse possa effettivamente fai quello che fa un vero Employeeo cerca metodi come void DoPaperwork()o bool TryDiscreetlyBrowseFacebook().


1
Grazie mille per la tua risposta! Credo che il primo punto nella tua risposta descriva definitivamente cosa succede nelle classi .NET, ma il secondo ha più valore (e tra l'altro è diverso), perché rivela meglio l'astrazione prevista: una cosa da usare rispetto a un cosa da descrivere. È stato difficile scegliere quale risposta accettare.
heltonbiker

4

In generale, un Infooggetto incapsula informazioni sullo stato di un oggetto in qualche momento . Se chiedessi al sistema di guardare un file e di fornirmi un FileInfooggetto associato alla sua dimensione, mi aspetterei che l'oggetto riporti la dimensione del file al momento della richiesta (o, per essere più precisi, la dimensione di il file in qualche momento tra quando è stata effettuata la chiamata e quando è tornata). Se la dimensione del file cambia tra il momento in cui la richiesta ritorna e il momento in cui l' FileInfooggetto viene esaminato, non mi aspetto che tale cambiamento si rifletta FileInfonell'oggetto.

Si noti che questo comportamento sarebbe molto diverso da quello di un Fileoggetto. Se una richiesta di apertura di un file su disco in modalità non esclusiva produce un Fileoggetto con una Sizeproprietà, mi aspetterei che il valore restituito cambierà quando cambia la dimensione del file su disco, poiché l' Fileoggetto non rappresenta semplicemente lo stato di un file: rappresenta il file stesso .

In molti casi, gli oggetti associati a una risorsa devono essere ripuliti quando i loro servizi non sono più necessari. Poiché gli *Infooggetti non si collegano alle risorse, non richiedono pulizia. Di conseguenza, nei casi in cui un Infooggetto soddisfi i requisiti di un client, potrebbe essere meglio che il codice ne utilizzi uno piuttosto che utilizzare un oggetto che rappresenterebbe la risorsa sottostante, ma la cui connessione a tale risorsa dovrebbe essere ripulita.


1
Questo non è esattamente sbagliato, ma non sembra spiegare molto bene i modelli di denominazione di .NET, poiché la Fileclasse non può essere istanziata e gli FileInfooggetti si aggiornano con il file system sottostante.
Nathan Tuggy,

@NathanTuggy Concordo sul fatto che questo non si collega bene con questa convenzione di denominazione in .NET, ma per quanto riguarda il mio problema attuale, credo che sia molto rilevante e possa indurre una soluzione coerente. Ho votato a favore di questa risposta, ma è difficile sceglierne solo una da accettare ...
heltonbiker

@NathanTuggy: non consiglierei l'uso di .NET come modello di alcun tipo di coerenza. È un framework buono e utile che racchiude molte buone idee, ma anche alcune cattive idee vengono catturate nel mix.
supercat

@supercat: Sicuro; sembra strano rispondere a una domanda sul "perché .NET fa le cose in questo modo?" con "sarebbe una buona idea fare le cose% THIS_WAY%".
Nathan Tuggy,

@NathanTuggy: non ricordavo i dettagli esatti delle classi in questione, ma pensavo FileInfosolo di avere informazioni acquisite staticamente. No? Inoltre, non ho mai avuto occasione di aprire i file in modalità non esclusiva, ma dovrebbe essere possibile e mi aspetterei che esista un metodo che riporti la dimensione corrente di un file, anche se l'oggetto utilizzato per l'apertura non è chiamato File(di solito mi basta usare ReadAllBytes, WriteAllBytes, ReadAllText, WriteAllText, etc.).
supercat

2

Considerando che i dispositivi effettivi (sensori e ricevitori) sono una cosa, e la loro rappresentazione nel software è un'altra, sto pensando di nominare alcune classi con il modello di nome del suffisso "Info".

Ad esempio, mentre a Sensorsarebbe una classe per rappresentare il sensore effettivo (quando è effettivamente collegato a un dispositivo funzionante), SensorInfoverrebbe utilizzato per rappresentare solo le caratteristiche di tale sensore. Ad esempio, dopo il salvataggio del file, vorrei serializzare un SensorInfonell'intestazione del file, anziché serializzare un Sensor, che non avrebbe nemmeno senso.

Non mi piace questa distinzione. Tutti gli oggetti sono "rappresentazioni nel software". Ecco cosa significa la parola "oggetto".

Ora, potrebbe avere senso separare le informazioni su una periferica dal codice effettivo che si interfaccia con la periferica. Quindi, ad esempio, un Sensor ha un SensorInfo, che contiene la maggior parte delle variabili di istanza, insieme ad alcuni metodi che non richiedono hardware, mentre la classe Sensor è responsabile dell'interazione effettiva con il sensore fisico. Non hai-a a Sensormeno che il tuo computer non abbia un sensore, ma potresti plausibilmente averlo-a SensorInfo.

Il problema è che questo tipo di design può essere generalizzato a (quasi) qualsiasi classe. Quindi devi stare attento. Ovviamente non avresti una SensorInfoInfolezione, per esempio. E se hai una Sensorvariabile, potresti trovarti a violare la legge di Demetra interagendo con il suo SensorInfomembro. Niente di tutto questo è fatale, ovviamente, ma il design delle API non è solo per gli autori delle biblioteche. Se mantieni la tua API pulita e semplice, il tuo codice sarà più gestibile.

Le risorse del filesystem come le directory, secondo me, sono molto vicine a questo limite. Ci sono alcune situazioni in cui vuoi descrivere una directory che non è accessibile localmente, vero, ma lo sviluppatore medio probabilmente non si trova in una di quelle situazioni. Complicare la struttura di classe in questo modo è, secondo me, inutile. Contrasta l'approccio di Python in pathlib: Esiste una singola classe che "molto probabilmente è ciò di cui hai bisogno" e varie classi ausiliarie che la maggior parte degli sviluppatori può tranquillamente ignorare. Se ne hai davvero bisogno, tuttavia, forniscono in gran parte la stessa interfaccia, solo con metodi concreti eliminati.


Grazie per la tua risposta, e complimenti per il tuo costrutto "have-a";) La tua risposta mi ricorda il disagio causato ad alcuni dal concetto "DTO" (Data Transfer Object) - o suo fratello l'oggetto Parametro - che è disapprovato da più fanatici OO ortodossi perché di solito non contiene metodi (dopo tutto è un oggetto di trasferimento dati ). In tal senso, e riassumendo anche ciò che altri hanno detto, penso che SensorInfosarebbe una sorta di DTO, e / o anche come una "istantanea", o anche una "specifica", che rappresenta solo la parte dati / stato Sensordell'oggetto reale che "potrebbe non essere nemmeno lì".
heltonbiker

Come con la maggior parte dei problemi di progettazione, non esiste una regola rigida e veloce. Nell'esempio pathlib che ho fornito, si PureWindowsPathcomporta un po 'come un oggetto Info, ma ha metodi per fare cose che non richiedono un sistema Windows (ad esempio prendere una sottodirectory, dividere un'estensione di file, ecc.). Questo è più utile del semplice fornire una struttura glorificata.
Kevin,

1
Ancora una volta, complimenti per la parte "struttura glorificata" :). Mi viene in mente una citazione correlata del libro "Clean Code" di zio Bob, capitolo 6: "I programmatori maturi sanno che l'idea che tutto sia un oggetto è un mito. A volte si vogliono davvero semplici strutture dati con procedure operative".
heltonbiker,

2

Direi che il contesto / dominio è importante, poiché abbiamo un codice di logica aziendale di alto livello e modelli di basso livello, componenti di architettura e così via ...

'Info', 'Dati', 'Manager', 'Oggetto', 'Classe', 'Modello', 'Controller' ecc. Possono essere suffissi puzzolenti, specialmente a un livello inferiore, poiché ogni oggetto ha alcune informazioni o dati, quindi tali informazioni non sono necessarie.

I nomi delle classi del dominio aziendale dovrebbero essere come tutti gli stakeholder ne parlano, non importa se sembra strano o non è un linguaggio corretto al 100%.

Buoni suffissi per le strutture di dati sono ad esempio 'Elenco', 'Mappa' e per i modelli di suggerimento 'Decoratore', 'Adattatore' se pensi che sia necessario.

Nel tuo scenario del sensore, non mi aspetterei SensorInfodi salvare qual è il tuo sensore, ma SensorSpec. Infoimho è più un'informazione derivata, come FileInfoè qualcosa come la dimensione, non si salva o il percorso del file che è costruito dal percorso e dal nome del file, ecc.

Un altro punto:

In Informatica ci sono solo due cose difficili: invalidare la cache e denominare le cose.

- Phil Karlton

Questo mi ricorda sempre di pensare a un nome solo per pochi secondi e se non ne trovo uno, uso nomi strani e li contrassegno "TODO". Posso sempre cambiarlo in seguito poiché il mio IDE fornisce supporto per il refactoring. Non è uno slogan aziendale che deve essere buono, ma solo un po 'di codice che possiamo cambiare ogni volta che vogliamo. Tienilo a mente.


1

ThingInfo può essere un ottimo proxy di sola lettura per la Cosa.

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

Proxy: "Fornisci un surrogato o un segnaposto per un altro oggetto per controllarne l'accesso."

In genere ThingInfo avrà proprietà pubbliche senza setter. Queste classi e i metodi sulla classe sono sicuri da usare e non eseguiranno alcuna modifica ai dati di supporto, all'oggetto o ad altri oggetti. Non si verificheranno cambiamenti di stato o altri effetti collaterali. Questi possono essere utilizzati per i report e i servizi Web o ovunque sia necessario disporre di informazioni sull'oggetto ma che si desideri limitare l'accesso all'oggetto reale stesso.

Usa ThingInfo ogni volta che è possibile e limita l'uso della Cosa reale ai tempi in cui devi effettivamente cambiare l'oggetto. Rende la lettura e il debug notevolmente più veloce quando ci si abitua a utilizzare questo modello.


Eccellente. Prima di oggi stavo analizzando le mie esigenze architettoniche, riguardo alle classi che ho usato nella domanda, e sono arrivato a questa stessa conclusione. Nel mio caso, ho un Receiverche riceve flussi di dati da molti Sensors. L'idea è: il ricevitore dovrebbe astrarre i sensori reali. Ma il problema è: ho bisogno delle informazioni del sensore per poterle scrivere nell'intestazione di un file. La soluzione: ognuno IReceiveravrà un elenco di SensorInfo. Se invio comandi al ricevitore che implicano la modifica dello stato del sensore, questi cambiamenti verranno riflessi (tramite getter) sul rispettivo SensorInfo.
heltonbiker

(continua ...) vale a dire: non parlo con i sensori stessi, parlo solo con il ricevitore tramite comandi di livello superiore e il ricevitore a sua volta parla con ciascuno dei sensori. Ma alla fine ho bisogno di informazioni dai sensori, che ottengo dall'istanza del ricevitore tramite la sua List<SensorInfo>proprietà di sola lettura.
heltonbiker

1

Finora nessuno in questa domanda sembra aver raccolto la vera ragione di questa convenzione di denominazione.

A DirectoryInfonon è la directory. È un DTO con dati sulla directory. Possono esserci molti di questi casi che descrivono la stessa directory. Non è un'entità. È un oggetto valore da buttare via. A DirectoryInfonon rappresenta la directory effettiva. Puoi anche pensarlo come un handle o controller per una directory.

Contrastalo con una classe chiamata Employee. Potrebbe trattarsi di un oggetto entità ORM ed è il singolo oggetto che descrive quel dipendente. Se fosse un oggetto valore senza identità, dovrebbe essere chiamato EmployeeInfo. An Employeerappresenta davvero il dipendente effettivo. Una classe DTO simile al valore chiamata EmployeeInfonon rappresenterebbe chiaramente il dipendente ma piuttosto lo descriverebbe o memorizzerebbe i dati al riguardo.

In realtà c'è un esempio nel BCL in cui entrambe le classi esistono: A ServiceControllerè una classe che descrive un servizio di Windows. Può esistere un numero qualsiasi di tali controller per ciascun servizio. Una ServiceBase(o una classe derivata) è il servizio effettivo e concettualmente non ha senso avere più istanze di esso per servizio distinto.


Sembra simile al Proxymodello menzionato da @Shmoken, vero?
heltonbiker,

@heltonbiker non deve necessariamente essere un proxy. Potrebbe essere un oggetto che non è collegato in alcun modo alla cosa che descrive. Ad esempio, potresti ottenere un EmployeeInfoda un servizio web. Questo non è un proxy. Ulteriore esempio: Un AddressInfoneppure non ha una cosa che può procura a causa di un indirizzo non è un'entità. È un valore autonomo.
usr

1
Capisco, ha senso!
heltonbiker,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.