Due strutture con gli stessi membri ma nomi diversi, è una buona idea?


49

Sto scrivendo un programma che prevede il lavoro con coordinate sia polari che cartesiane.

Ha senso creare due diverse strutture per ogni tipo di punti, uno con Xe Ymembri e uno con Re Thetamembri.

O è troppo ed è meglio avere solo una struttura con firste secondcome membri.

Quello che sto scrivendo è semplice e non cambierà molto. Ma sono curioso di sapere cosa c'è di meglio dal punto di vista del design.

Penso che la prima opzione sia migliore. Sembra più leggibile e otterrò il vantaggio del controllo del tipo.


11
Creo sempre una nuova struttura / classe per scopo. Hai bisogno di un vettore 3d, crea una struttura 3d_vector con tre galleggianti. Hai bisogno di una rappresentazione uvw, crea una struttura texture_coords con tre float. Hai bisogno di una posizione in un ambiente 3d, crea una posizione strutt con tre galleggianti. Ottieni il punto. Consente una leggibilità molto migliore rispetto all'uso della stessa cosa dappertutto. Se sei infastidito dalla definizione della stessa cosa più volte, usa una struttura 3 float di base e definisci più nomi per avere la stessa struttura.
Kevin,

Se hanno alcuni metodi comuni, forse uno. Avresti mai bisogno di confrontare l'uguaglianza dei due?
paparazzo,

8
@Sidney A meno che tu non abbia assolutamente bisogno della funzionalità, non lo farei. È necessario eseguire un'operazione sin / arcsin per la conversione tra le due rappresentazioni. Questo introdurrà un po 'di bitrot nei bit meno significativi ogni volta che esegui la conversione. Sono quasi certo che finiresti con un dolore simile a quello che ho passato cercando di gestire una classe che forniva sia una frequenza di eventi che un intervallo tra gli eventi (xe 1 / x) di qualcosa. Tracciare quale rappresentazione è cannonica nella classe e gestire tutti i mal di testa arrotondati non è qualcosa che vorrei fare di nuovo.
Dan Neely,

3
Un buon esempio di un tipo di dati che può rappresentare molte cose è la stringa, ma "tipy stringy" è un antipattern. Hai scritto il tuo esempio. Prova a implementare il prodotto punto per un tipo che supporta entrambi i sistemi di coordinate.
Nathan Cooper,

1
Dovresti cercare di usare tipi diversi ogni volta che è applicabile, per ottenere i vantaggi della sicurezza dei tipi - questo ti impedirà di inviare il tipo sbagliato a / da una funzione (usando il controllo della correttezza del tempo di compilazione, a seconda del tuo linguaggio di programmazione). Faciliterà la manutenzione perché puoi trovare tutti gli usi reali di un certo tipo (senza ottenere usi errati a causa della fusione dei tipi).
Erik Eidt,

Risposte:


17

Ho visto entrambe le soluzioni, quindi dipende decisamente dal contesto.

Per leggibilità, avere più strutture come suggerisci è molto efficace. Tuttavia, in alcuni ambienti, vuoi fare manipolazioni comuni a queste strutture e ti ritrovi a duplicare il codice, come le operazioni vettoriali a matrice *. Può essere frustrante quando la particolare operazione non è disponibile nel tuo stile di vettore perché nessuno l'ha portato lì.

La soluzione estrema (che alla fine abbiamo dato) è quella di avere una classe base basata su modelli CRTP con funzioni get <0> () get <1> () e get <2> () per ottenere gli elementi in modo generico. Tali funzioni sono quindi definite nella struttura cartesiana o polare che deriva da questa classe base. Risolve tutti i problemi, ma ha un prezzo piuttosto sciocco: imparare la metaprogrammazione dei template. Tuttavia, se la metaprogrammazione dei modelli è già un gioco equo per il tuo progetto, potrebbe essere una buona partita.


1
La tua risposta è molto interessante è possibile fornire un esempio?
Moha l'onnipotente cammello l'

1
Posso fornire un esempio di ciò che ho fatto: polari filtranti FIR, cartesiani e loro vettori. La matematica era abbastanza simile, sans angle (un) wrapping, il codice aveva comunque alcune parti duplicate per motivi di prestazioni e abbiamo usato modelli per cui era lo stesso. Usati nomi diversi per tutte le cose. La "soluzione estrema" di Cort avrebbe potuto salvare alcune duplicazioni, ma non quasi tutte.
Eugene Ryabtsev,

1
La mia prima reazione è stata che una situazione come questa sarebbe stata meglio risolta con il casting, ma si è rivelata in qualche modo rischiosa .
200_successo

114

Sì, ha molto senso.

Il valore di uno struct non è solo che incapsula i dati con un nome pratico. Il valore è che codifica le tue intenzioni in modo che il compilatore possa aiutarti a verificare che un giorno non le violerai (ad esempio, scambiare un set di coordinate polari per un set di coordinate cartesiane).

Le persone sono cattive nel ricordare dettagli così sgraziati, ma brave nel creare piani audaci e inventivi. I computer sono bravi a sgridare i dettagli e cattivi a piani creativi. Pertanto è sempre una buona idea trasferire al computer tutta la manutenzione dei dettagli, lasciando la mente libera di lavorare sul piano generale.


6
+1 Una descrizione perfetta dell'uso del computer per fare ciò che i computer sono bravi a fare e di lasciare che il cervello si concentri sul proprio lavoro.
BrianH,

8
"I programmi dovrebbero essere scritti per essere letti dalle persone e solo per inciso per l'esecuzione delle macchine." - da "Struttura e interpretazione dei programmi per computer" di Abelson e Sussman.
hlovdal

18

Sì, mentre sia cartesiani che polari sono (al loro posto) schemi di rappresentazione delle coordinate eminentemente sensibili, idealmente non dovrebbero mai essere mescolati (se hai un punto cartesiano {1,1}, è un punto molto diverso da polare {1,1 }).

A seconda delle esigenze, può anche essere la pena di implementare un'interfaccia Coordinate, con metodi come X(), Y(), Displacement()e Angle()(o, eventualmente, Radius()e Theta(), a seconda).


È ancora più importante se l'OP sta ricavando classi da tali strutture, poiché le operazioni sulle coordinate cartesiane e polari sono diverse.
Mindwin,

1
+1 per l'ultimo paragrafo, questa è la soluzione ideale. Un punto è lo spazio è un oggetto; la rappresentazione interna di quel punto non dovrebbe avere importanza. Naturalmente, le preoccupazioni del mondo reale (prestazioni, errori di arrotondamento) potrebbero far sì che ciò contenga. Tutto dipende da cosa viene utilizzato.
BlueRaja - Danny Pflughoeft,

E anche, per questo esempio, è improbabile che cambi, ma se fossero altre 2 classi, nulla ti dice che potrebbero divergere in alcuni punti.
coloranti

8

Alla fine, l'obiettivo della programmazione è di attivare / disattivare i bit del transistor per fare un lavoro utile. Ma pensare a un livello così basso porterebbe a una follia ingestibile, motivo per cui esistono linguaggi di programmazione di livello superiore per aiutarti a nascondere la complessità.

Se fai solo una struttura con membri nominati firste second, allora i nomi non significano nulla; li tratteresti essenzialmente come indirizzi di memoria. Ciò vanifica lo scopo del linguaggio di programmazione di alto livello.

Inoltre, solo perché sono tutti rappresentabili come a doublenon significa che puoi usarli in modo intercambiabile. Ad esempio, θ è un angolo senza dimensioni, mentre y ha unità di lunghezza. Poiché i tipi non sono sostituibili logicamente, dovrebbero essere due strutture incompatibili.

Se hai davvero bisogno di giocare trucchi per riutilizzare la memoria - e quasi sicuramente non dovresti - puoi usare un uniontipo in C per chiarire le tue intenzioni.


Migliore risposta, IMHO
Dean Radcliffe,

2

In primo luogo, hanno entrambi esplicitamente, secondo la risposta del tutto solida di @ Kilian-foth.

Tuttavia, vorrei aggiungere:

Chiedi: hai davvero operazioni generiche per entrambi se considerate come coppie di double? Notare che non equivale a dire che si hanno operazioni che si applicano ad entrambi nei loro termini. Ad esempio 'trama (Coord)' si preoccupa se Coordè polare o cartesiano. D'altra parte, persistere nel file tratta i dati così come sono. Se davvero avete operazioni generiche, prendere in considerazione sia la definizione di una classe base, o la definizione di un convertitore a una std::pair<double, double>o tupleo qualsiasi altra cosa che avete nella vostra lingua.

Inoltre, un approccio potrebbe essere quello di considerare un tipo di Coordinata come più fondamentale e l'altro come un semplice supporto per l'interazione dell'utente o esterna.

Quindi potresti assicurarti che tutte le operazioni fondamentali siano codificate Cartesiane quindi fornire supporto per la conversione Polarin Cartesian. Ciò evita di implementare diverse versioni di molte operazioni.


1

Una possibile soluzione, a seconda della lingua e se si sa che entrambe le classi avranno metodi e operazioni simili, sarebbe quella di definire la classe una volta e utilizzare gli alias di tipo per nominare esplicitamente i tipi in modo diverso.

Ciò ha anche il vantaggio che fino a quando le classi sono esattamente le stesse, è possibile mantenerne solo una, ma non appena è necessario modificarle, non è necessario modificare il codice utilizzandole poiché i tipi sono già stati utilizzati distincly.

Un'altra opzione, a seconda dell'uso delle classi (se hai bisogno di polimorfismo e simili) è quella di utilizzare l'eredità pubblica per entrambi i nuovi tipi, quindi hanno la stessa interfaccia pubblica del tipo comune che entrambi rappresentano. Ciò consente anche un'evoluzione separata dei tipi.


I nomi dei membri in entrambe le classi non sono gli stessi. in realtà i nomi sono l'unica differenza tra le due classi
Moha l'onnipotente cammello,

@ Mhd.Tahawi Potresti implementare getter e setter con i nomi appropriati, assicurandoti che la classe che stai usando sia la stessa, ma fornendo nomi appropriati per le operazioni che vuoi usare. Diventa un po 'più dettagliato, ma dovrai duplicare meno codice.
Svalorzen,

0

Credo che avere gli stessi nomi dei membri sia una cattiva idea in questo caso, perché rende il codice più soggetto a errori.

Immagina lo scenario: hai un paio di punti cartesiani: pntA e pntB. Quindi decidi, per qualche motivo, che dovrebbero essere meglio rappresentati in coordinate polari e cambiare la dichiarazione e il costruttore.

Ora, se tutte le tue operazioni erano solo chiamate di metodo come:

double distance = pntA.distanceFrom(pntB);

allora stai bene. Ma cosa succede se si utilizzano esplicitamente i membri? Confrontare

double leftMargin = abs(pntA.x - pntB.x);
double leftMargin = abs(pntA.first - pntB.first);

Nel primo caso, il codice non verrà compilato. Vedrai immediatamente l'errore e sarai in grado di risolverlo. Ma se hai gli stessi nomi dei membri, l'errore sarà solo a livello logico, molto più difficile da rilevare.

Se si scrive in un linguaggio non orientato agli oggetti, è ancora più semplice passare una struttura errata alla funzione. Cosa ti impedisce di scrivere il seguente codice?

double distance = calculate_distance_polar(cartesianPointA, polarPointB);

Diversi tipi di dati, d'altra parte, ti permetterebbero di trovare l'errore durante la compilazione.

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.