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

49

Sto scrivendo un programma che implica il lavoro con coordinate polari e cartesiane.

Ha senso creare due diverse strutture per ogni tipo di punti, uno con X e Y membri e uno con R e Theta membri.

O è troppo ed è meglio avere solo una struttura con first e second come membri.

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

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

    
posta Mhd.Tahawi 10.11.2015 - 09:36
fonte

7 risposte

17

Ho visto entrambe le soluzioni, quindi è decisamente dipendente dal contesto.

Per la leggibilità, avere più strutture come suggerisci è molto efficace. Tuttavia, in alcuni ambienti, si vogliono fare manipolazioni comuni a queste strutture e ci si ritrova a duplicare codice, ad esempio operazioni con la matrice * vettoriale. Può diventare frustrante quando la particolare operazione non è disponibile nel tuo gusto del vettore perché nessuno lo ha portato lì.

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

    
risposta data 10.11.2015 - 18:18
fonte
114

Sì, ha molto senso.

Il valore di una struct non è solo quello che incapsula i dati sotto un nome pratico. Il valore è che codifica le tue intenzioni in modo che il compilatore possa aiutarti a verificare che non li violerai un giorno (ad esempio, scambia un insieme di coordinate polari per un insieme di coordinate cartesiane).

Le persone sono cattive nel ricordare dettagli così piccoli, ma bravi nel creare piani audaci e inventivi. I computer sono bravi a dettagli insignificanti e cattivi a piani creativi. Pertanto è sempre una buona idea spostare al computer la manutenzione con il minimo dettaglio, lasciando la tua mente libera di lavorare sul grande piano.

    
risposta data 10.11.2015 - 09:40
fonte
18

Sì, mentre sia Cartesian sia Polar sono (al loro posto) schemi di rappresentazione a coordinate eminentemente sensibili, dovrebbero idealmente non essere mai mischiati (se hai un punto cartesiano {1,1}, è un punto molto diverso da Polar { 1,1}).

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

    
risposta data 10.11.2015 - 11:15
fonte
8

Alla fine, l'obiettivo della programmazione è di commutare i bit dei 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 si crea una struttura con membri denominati first e second , 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 double non significa che puoi usarli in modo intercambiabile. Ad esempio, θ è un angolo senza dimensione, mentre y ha unità di lunghezza. Poiché i tipi non sono logicamente sostituibili, dovrebbero essere due strutture incompatibili.

Se veramente hai bisogno di trucchi per riutilizzare la memoria - e quasi certamente non dovresti - puoi usare un union di tipo in C per rendere chiara l'intenzione.

    
risposta data 10.11.2015 - 21:41
fonte
2

In primo luogo, ho entrambi esplicitamente, come per la risposta completamente sana di @ Kilian-foth.

Tuttavia, vorrei aggiungere:

Chiedi: hai davvero delle operazioni che sono generiche per entrambe quando considerate come coppie di double ? Nota che non è come dire che hai operazioni che si applicano ad entrambi nei loro stessi termini. Ad esempio, "plot (Coord)" si preoccupa se Coord è Polar o Cartesiano. D'altra parte, persistere nel file tratta semplicemente i dati così come sono. Se hai davvero operazioni generiche, considera la definizione di una classe base o la definizione di un convertitore in std::pair<double, double> o tuple o qualsiasi altra cosa tu abbia nella tua lingua.

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

Quindi potresti garantire che tutte le operazioni fondamentali siano codificate per Cartesian e quindi fornire supporto per convertire Polar in Cartesian . Questo evita di implementare diverse versioni di molte operazioni.

    
risposta data 10.11.2015 - 23:34
fonte
1

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

Questo ha anche il vantaggio che finché le classi sono esattamente le stesse, puoi mantenerne solo una, ma non appena hai bisogno di cambiarle, non devi modificare il codice usandole poiché i tipi hanno già usato distintamente.

Un'altra opzione, che dipende ancora dall'uso delle classi (se hai bisogno di polimorfismo e così via) è di usare l'ereditarietà pubblica per entrambi i nuovi tipi, in modo che abbiano la stessa interfaccia pubblica del tipo comune che entrambi rappresentano. Ciò consente anche un'evoluzione separata dei tipi.

    
risposta data 11.11.2015 - 12:28
fonte
0

Credo che avere lo stesso nome membro sia una cattiva idea in questo caso, perché rende il codice più incline agli errori.

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

Ora, se tutte le tue operazioni fossero solo chiamate di metodi come:

double distance = pntA.distanceFrom(pntB);

allora stai bene. Ma cosa succede se hai usato i membri esplicitamente? Confronta

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 si hanno gli stessi nomi dei membri, l'errore sarà solo a livello logico, molto più difficile da rilevare.

Se scrivi in un linguaggio non orientato agli oggetti, è ancora più semplice passare la struttura sbagliata 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 consentirebbero di trovare l'errore durante la compilazione.

    
risposta data 11.11.2015 - 16:54
fonte

Leggi altre domande sui tag