Dos estructuras con los mismos miembros pero nombres diferentes, ¿es una buena idea?

49

Estoy escribiendo un programa que implica trabajar con coordenadas polares y cartesianas.

¿Tiene sentido crear dos estructuras diferentes para cada tipo de puntos, uno con Xy Ymiembros y otro con Ry Thetamiembros.

¿O es demasiado y es mejor tener solo una estructura con firsty secondcomo miembros?

Lo que estoy escribiendo es simple y no cambiará mucho. Pero tengo curiosidad por saber qué es mejor desde el punto de vista del diseño.

Estoy pensando que la primera opción es mejor. Parece más legible y obtendré el beneficio de la verificación de tipos.

Moha el todopoderoso camello
fuente
11
Siempre creo una nueva estructura / clase por propósito. Necesita un vector 3d, cree una estructura 3d_vector con tres flotadores. Necesita una representación uvw, cree una estructura texture_coords con tres flotantes. Necesita una posición en un entorno 3D, cree una posición de estructura con tres flotadores. Tú entiendes. Permite una legibilidad mucho mejor que usar lo mismo en todas partes. Si le molesta definir la misma cosa varias veces, use una estructura de 3 flotantes base y defina varios nombres para que sean la misma estructura.
Kevin
Si tienen algunos métodos comunes, tal vez uno. ¿Alguna vez necesitarías comparar la igualdad de los dos?
paparazzo
8
@Sidney A menos que necesites absolutamente la funcionalidad que yo no necesitaría. Necesita hacer una operación sin / arcsin para convertir entre las dos representaciones. Esto introducirá un bit de bitrot en los bits menos significativos cada vez que realice la conversión. Estoy casi seguro de que terminarías con un dolor similar al que pasé al tratar de lidiar con una clase que proporcionaba tanto una frecuencia de eventos como un tiempo entre eventos (x y 1 / x) representación de algo. Hacer un seguimiento de qué representación es canónica en la clase y lidiar con todos los dolores de cabeza redondeados no es algo que quisiera volver a hacer.
Dan Neely
3
Un buen ejemplo de un tipo de datos que puede representar muchas cosas es la cadena, pero "stringy typed" es un antipatrón. Escribe tu ejemplo. Intente implementar un producto punto para un tipo que admita ambos sistemas de coordenadas.
Nathan Cooper
1
Debe esforzarse por usar diferentes tipos siempre que sea aplicable, para obtener los beneficios de la seguridad de tipos; esto evitará que envíe el tipo incorrecto a / desde una función (utilizando la verificación de corrección de tiempo de compilación, dependiendo de su lenguaje de programación). Facilitará el mantenimiento porque puede encontrar todos los usos verdaderos de algún tipo (sin obtener usos erróneos debido a que los tipos se combinan).
Erik Eidt

Respuestas:

17

He visto ambas soluciones, por lo que definitivamente depende del contexto.

Para facilitar la lectura, tener múltiples estructuras como sugiere es muy efectivo. Sin embargo, en algunos entornos, desea realizar manipulaciones comunes a estas estructuras, y se encuentra duplicando el código, como las operaciones de vector de matriz *. Puede ser frustrante cuando la operación particular no está disponible en su sabor de vector porque nadie lo portó allí.

La solución extrema (a la que finalmente nos rendimos) es tener una clase base con plantilla CRTP con funciones get <0> () get <1> () y get <2> () para obtener los elementos de manera genérica. Esas funciones se definen luego en la estructura cartesiana o polar que se deriva de esta clase base. Resuelve todos los problemas, pero tiene un precio bastante tonto: tener que aprender la metaprogramación de plantillas. Sin embargo, si la metaprogramación de plantillas ya es un juego justo para su proyecto, podría ser una buena combinación.

Cort Ammon
fuente
1
Tu respuesta es muy interesante. ¿Es posible dar un ejemplo?
Moha el todopoderoso camello
1
Puedo ofrecer un ejemplo de lo que he hecho: polares filtrantes FIR, cartesianos y sus vectores. La matemática era bastante similar, sin ángulo (sin) envoltura, el código tenía algunas partes duplicadas de todos modos por razones de rendimiento y utilizamos plantillas para donde era lo mismo. Usé diferentes nombres para todas las cosas. La "solución extrema" de Cort podría haber guardado algunas duplicaciones, pero no casi todas.
Eugene Ryabtsev
1
Mi primera reacción fue que una situación como esta se resolvería mejor con el casting, pero resulta ser algo arriesgado .
200_success
114

Sí, tiene mucho sentido.

El valor de una estructura no es solo que encapsula datos bajo un nombre práctico. El valor es que codifica sus intenciones para que el compilador pueda ayudarlo a verificar que no las viole algún día (por ejemplo, confunda un conjunto de coordenadas polares con un conjunto de coordenadas cartesianas).

Las personas son malas para recordar detalles tan insignificantes, pero buenos para crear planes atrevidos e ingeniosos. Las computadoras son buenas en detalles insignificantes y malas en planes creativos. Por lo tanto, siempre es una buena idea trasladar la mayor cantidad de mantenimiento a la computadora, dejando su mente libre para trabajar en el gran plan.

Kilian Foth
fuente
66
+1 Una descripción perfecta del uso de la computadora para hacer lo que las computadoras son buenas para hacer, y dejar que tu cerebro se concentre en tu propio trabajo.
BrianH
8
"Los programas deben escribirse para que la gente los lea, y solo de manera incidental para que las máquinas los ejecuten". - de "Estructura e interpretación de programas de computadora" por Abelson y Sussman.
hlovdal
18

Sí, aunque tanto Cartesian como Polar son (en su lugar) esquemas de representación de coordenadas eminentemente sensibles, idealmente nunca deberían mezclarse (si tiene un punto Cartesian {1,1}, es un punto muy diferente de Polar {1,1 }).

Dependiendo de sus necesidades, sino que también puede valer la pena implementar una interfaz de coordenadas, con métodos como X(), Y(), Displacement()y Angle()(o, posiblemente, Radius()y Theta(), dependiendo).

Vatine
fuente
Es aún más importante si el OP está haciendo clases a partir de esas estructuras, ya que las operaciones en coordenadas cartesianas y polares son diferentes.
Mindwin
1
+1 para el último párrafo, esa es la solución ideal. Un punto es el espacio es un objeto; la representación interna de ese punto no debería importar. Por supuesto, las preocupaciones del mundo real (rendimiento, errores de redondeo) pueden hacer que importe. Todo depende de para qué se esté utilizando.
BlueRaja - Danny Pflughoeft
Y también, para este ejemplo, es poco probable que cambie, pero si fueran otras 2 clases, nada te dice que podrían divergir en algunos puntos.
dyesdyes
8

Al final, el objetivo de la programación es alternar los bits del transistor para hacer un trabajo útil. Pero pensar a un nivel tan bajo conduciría a una locura inmanejable, por lo que existen lenguajes de programación de alto nivel para ayudarlo a ocultar la complejidad.

Si solo crea una estructura con miembros nombrados firsty second, entonces los nombres no significan nada; esencialmente los trataría como direcciones de memoria. Eso anula el propósito del lenguaje de programación de alto nivel.

Además, el hecho de que todos sean representables como un doubleno significa que puede usarlos indistintamente. Por ejemplo, θ es un ángulo adimensional, mientras que y tiene unidades de longitud. Como los tipos no son lógicamente sustituibles, deberían ser dos estructuras incompatibles.

Si realmente necesita jugar trucos de reutilización de memoria, y seguramente no debería, puede usar un uniontipo en C para aclarar su intención.

200_success
fuente
La mejor respuesta, en mi humilde opinión
Dean Radcliffe
2

En primer lugar, tenga ambos explícitamente, según la respuesta completamente sólida de @ Kilian-foth.

Sin embargo, me gustaría agregar:

Pregunte: ¿Realmente tienen operaciones que son genéricas para ambos cuando se consideran pares de double? Tenga en cuenta que esto no es lo mismo que decir que tiene operaciones que se aplican a ambos en sus propios términos. Por ejemplo, 'plot (Coord)' le importa si Coordes polar o cartesiano. Por otro lado, persistir en el archivo solo trata los datos como son. Si realmente tiene operaciones genéricas, considere la posibilidad de definir, ya sea una clase base, o la definición de un convertidor a una std::pair<double, double>o tupleo lo que usted tiene en su idioma.

Además, un enfoque podría ser tratar un tipo de Coordenada como más fundamental y el otro simplemente como soporte para la interacción externa o del usuario.

Por lo tanto, puede asegurarse de que todas las operaciones fundamentales estén codificadas Cartesiany luego brindar soporte para la conversión Polara Cartesian. Esto evita implementar diferentes versiones de muchas operaciones.

Keith
fuente
1

Una posible solución, dependiendo del idioma y si sabe que ambas clases tendrán métodos y operaciones similares, sería definir la clase una vez y usar alias de tipo para nombrar explícitamente los tipos de manera diferente.

Esto también tiene la ventaja de que, siempre y cuando las clases sean exactamente iguales, puede mantener solo una, pero tan pronto como necesite cambiarlas, no necesita modificar el código que las usa, ya que los tipos ya se han utilizado. distintamente

Otra opción, dependiendo nuevamente del uso de las clases (si necesita polimorfismo y tal) es usar la herencia pública para ambos tipos nuevos, de modo que tengan la misma interfaz pública que el tipo común que ambos representan. Esto también permite una evolución separada de los tipos.

Svalorzen
fuente
Los nombres de los miembros en ambas clases no son los mismos. en realidad los nombres son la única diferencia entre las dos clases
Moha, el todopoderoso camello, el
@ Mhd.Tahawi Puede implementar getters y setters con los nombres apropiados, asegurándose de que la clase que está utilizando sea la misma, pero proporcionando nombres apropiados para las operaciones que desea usar. Se vuelve un poco más detallado, pero tendrá que duplicar menos código.
Svalorzen
0

Creo que tener los mismos nombres de miembros es una mala idea en este caso, porque hace que el código sea más propenso a errores.

Imagine el escenario: tiene un par de puntos cartesianos: pntA y pntB. Luego decide, por alguna razón, que deberían estar mejor representados en coordenadas polares, y cambiar la declaración y el constructor.

Ahora, si todas sus operaciones fueran solo llamadas a métodos como:

double distance = pntA.distanceFrom(pntB);

Entonces estás bien. Pero, ¿qué pasa si usaste los miembros explícitamente? Comparar

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

En el primer caso, el código no se compilará. Verá el error inmediatamente y podrá solucionarlo. Pero si tiene los mismos nombres de miembros, el error será solo en el nivel lógico, mucho más difícil de detectar.

Si escribe en un lenguaje no orientado a objetos, es aún más fácil pasar una estructura incorrecta a la función. ¿Qué te impide escribir el siguiente código?

double distance = calculate_distance_polar(cartesianPointA, polarPointB);

Los diferentes tipos de datos, por otro lado, le permitirán encontrar el error durante la compilación.

IMil
fuente