Duas estruturas com os mesmos membros, mas com nomes diferentes, é uma boa ideia?

49

Estou escrevendo um programa que envolve trabalhar com coordenadas polares e cartesianas.

Faz sentido criar duas estruturas diferentes para cada tipo de ponto, um com Xe Ymembros e outro com Re Thetamembros.

Ou é demais e é melhor ter apenas uma estrutura com firste secondcomo membros.

O que estou escrevendo é simples e não vai mudar muito. Mas estou curioso para saber o que é melhor do ponto de vista do design.

Eu estou pensando que a primeira opção é melhor. Parece mais legível e terei o benefício da verificação de tipo.

Moha, o todo-poderoso camelo
fonte
11
Eu sempre crio uma nova estrutura / classe por finalidade. Precisa de um vetor 3d, crie um struct 3d_vector com três carros alegóricos. Precisa de uma representação uvw, crie uma estrutura texture_coords com três carros alegóricos. Precisa de uma posição em um ambiente 3D, crie uma posição struct com três carros alegóricos. Você entendeu. Ele permite uma legibilidade muito melhor do que usar a mesma coisa todo. Se você se incomodar em definir a mesma coisa várias vezes, use uma estrutura 3-float básica e defina vários nomes para serem a mesma estrutura.
Kevin
Se eles têm alguns métodos comuns, talvez um. Você precisaria comparar a igualdade dos dois?
Paparazzo
8
@ Sidney A menos que você precise absolutamente da funcionalidade que eu não precisaria. Você precisa fazer uma operação sin / arcsin para converter entre as duas representações. Isso introduzirá um pouco de bitrot nos bits menos significativos toda vez que você fizer a conversão. Estou quase certo de que você acabaria com uma dor semelhante ao que passei ao tentar lidar com uma classe que fornecia uma frequência de eventos e um tempo entre eventos (x e 1 / x) de algo. Rastrear qual representação é canônica na classe e lidar com todas as dores de cabeça arredondadas não é algo que eu gostaria de fazer novamente.
11555 Dan Neely
3
Um bom exemplo de um tipo de dados que pode representar muitas coisas é a cadeia de caracteres, mas "tipo de cadeia de caracteres" é um antipadrão. Escreva seu exemplo. Tente implementar o produto escalar para um tipo que suporte os dois sistemas de coordenadas.
Nathan Cooper
11
Você deve se esforçar para usar tipos diferentes sempre que aplicável, para obter os benefícios da segurança de tipos - isso impedirá o envio do tipo errado para / de uma função (usando a verificação de correção do tempo de compilação, dependendo da linguagem de programação). Isso facilitará a manutenção, pois você pode encontrar todos os usos verdadeiros de algum tipo (sem obter usos incorretos devido à fusão de tipos).
Erik Eidt

Respostas:

17

Eu vi as duas soluções, por isso é definitivamente dependente do contexto.

Para facilitar a leitura, ter várias estruturas, como você sugere, é muito eficaz. No entanto, em alguns ambientes, você deseja fazer manipulações comuns a essas estruturas e se vê duplicando código, como operações vetoriais matriciais *. Pode ser frustrante quando a operação específica não está disponível no seu sabor de vetor, porque ninguém a transportou para lá.

A solução extrema (na qual acabamos cedendo) é ter uma classe base modelada por CRTP com funções get <0> () get <1> () e get <2> () para obter os elementos de maneira genérica. Essas funções são então definidas na estrutura cartesiana ou polar que deriva dessa classe base. Ele resolve todos os problemas, mas tem um preço bastante tolo: ter que aprender a metaprogramação de modelos. No entanto, se a metaprogramação de modelos já é um jogo justo para o seu projeto, pode ser uma boa combinação.

Cort Ammon
fonte
11
Sua resposta é muito interessante. é possível dar um exemplo?
Moha o camelo almighty
11
Posso citar um exemplo do que fiz: polares de filtragem FIR, cartesianos e vetores dos mesmos. A matemática era bastante semelhante, sem quebra (ângulo) (sem), o código teve algumas partes duplicadas de qualquer maneira por razões de desempenho e usamos modelos para onde era o mesmo. Usou nomes diferentes para todas as coisas. A "solução extrema" de Cort poderia ter poupado algumas duplicações, mas não quase todas.
Eugene Ryabtsev 11/11
11
Minha primeira reação foi que uma situação como essa seria melhor resolvida pelo elenco, mas acaba sendo um pouco arriscada .
200_success
114

Sim, faz muito sentido.

O valor de uma estrutura não é apenas o fato de encapsular dados sob um nome útil. O valor é que ele codifica suas intenções para que o compilador possa ajudá-lo a verificar se você não as violou algum dia (por exemplo, confunda um conjunto de coordenadas polares com um conjunto de coordenadas cartesianas).

As pessoas são ruins em lembrar esses detalhes, mas boas em criar planos ousados ​​e inventivos. Os computadores são bons em detalhes e ruins em planos criativos. Portanto, é sempre uma boa ideia transferir o máximo de manutenção possível para o computador, deixando sua mente livre para trabalhar no grande plano.

Kilian Foth
fonte
6
+1 Uma descrição perfeita do uso do computador para fazer o que os computadores são bons em fazer e deixar seu cérebro se concentrar em seu próprio trabalho.
BrianH
8
"Os programas devem ser escritos para as pessoas lerem, e apenas acidentalmente para as máquinas executarem". - de "Estrutura e Interpretação de Programas de Computador", de Abelson e Sussman.
hlovdal
18

Sim, embora ambos os cartesianos e os polares sejam (em seu lugar) esquemas de representação de coordenadas eminentemente sensíveis, eles idealmente nunca devem ser misturados (se você tiver um ponto cartesiano {1,1}, é um ponto muito diferente do polar {1,1 }).

Dependendo de suas necessidades, ele também pode valer a pena implementar uma Coordenar interface, com métodos como X(), Y(), Displacement()e Angle()(ou, eventualmente, Radius()e Theta(), dependendo).

Vatine
fonte
É ainda mais importante se o OP estiver criando classes com essas estruturas, pois as operações nas coordenadas cartesiana e polar são diferentes.
Mindwin
11
+1 no último parágrafo, essa é a solução ideal. Um ponto é o espaço é um objeto; a representação interna desse ponto não deve importar. Obviamente, preocupações do mundo real (desempenho, erros de arredondamento) podem fazer com que isso importe. Tudo depende de para que isso está sendo usado.
BlueRaja - Danny Pflughoeft
E também, neste exemplo, é improvável que mude, mas se fossem duas outras classes, nada indica que elas podem divergir em alguns momentos.
dyesdyes
8

No final, o objetivo da programação é alternar os bits do transistor para realizar um trabalho útil. Mas pensar em um nível tão baixo levaria a uma loucura incontrolável, e é por isso que existem linguagens de programação de nível superior para ajudá-lo a esconder a complexidade.

Se você apenas criar uma estrutura com membros nomeados firste second, então os nomes não significam nada; você os trataria essencialmente como endereços de memória. Isso anula o objetivo da linguagem de programação de alto nível.

Além disso, apenas porque todos eles são representáveis ​​como doublenão significa que você pode usá-los de forma intercambiável. Por exemplo, θ é um ângulo sem dimensão, enquanto y tem unidades de comprimento. Como os tipos não são logicamente substituíveis, eles devem ser duas estruturas incompatíveis.

Se você realmente precisa executar truques de reutilização de memória - e quase certamente não deveria - você pode usar um uniontipo em C para deixar sua intenção clara.

200_success
fonte
Melhor resposta, IMHO
Dean Radcliffe
2

Em primeiro lugar, tenha ambos explicitamente, conforme a resposta totalmente sólida de @ Kilian-foth.

No entanto, gostaria de adicionar:

Pergunte: Você realmente tem operações genéricas para ambos quando consideradas como pares de double? Observe que isso não é o mesmo que dizer que você possui operações que se aplicam a ambos em seus próprios termos. Por exemplo, 'plot (Coord)' se importa se Coordé polar ou cartesiano. Por outro lado, persistir no arquivo apenas trata os dados como estão. Se você realmente tem operações genéricos, considere quer definir uma classe base, ou a definição de um conversor a um std::pair<double, double>ou tupleou o que você tem no seu idioma.

Além disso, uma abordagem pode ser tratar um tipo de coordenada como mais fundamental e a outra apenas como suporte à interação do usuário ou externa.

Portanto, você pode garantir que todas as operações fundamentais sejam codificadas Cartesiane fornecer suporte para a conversão Polarpara Cartesian. Isso evita a implementação de versões diferentes de muitas operações.

Keith
fonte
1

Uma solução possível, dependendo do idioma e se você souber que ambas as classes terão métodos e operações semelhantes, seria definir a classe uma vez e usar aliases de tipo para nomear explicitamente os tipos de maneira diferente.

Isso também tem a vantagem de que, desde que as classes sejam exatamente as mesmas, você poderá manter apenas uma, mas assim que precisar alterá-las, não será necessário modificar o código usando-as, pois os tipos já foram usados. distintamente.

Outra opção, dependendo novamente do uso das classes (se você precisar de polimorfismo e outros), é usar herança pública para os dois novos tipos, para que eles tenham a mesma interface pública que o tipo comum que ambos representam. Isso também permite a evolução separada dos tipos.

Svalorzen
fonte
Os nomes dos membros das duas classes não são os mesmos. na verdade, os nomes são a única diferença entre as duas classes
Moha o camelo almighty
@ Mhd.Tahawi Você pode implementar getters e setters com os nomes apropriados, garantindo que a classe que você está usando seja a mesma, mas fornecendo nomes apropriados para as operações que você deseja usar. Torna-se um pouco mais detalhado, mas você terá que duplicar menos código.
Svalorzen
0

Acredito que ter o mesmo nome de membro seja uma má ideia nesse caso, porque torna o código mais suscetível a erros.

Imagine o cenário: você tem alguns pontos cartesianos: pntA e pntB. Então você decide, por algum motivo, que elas devem ser melhor representadas em coordenadas polares e altera a declaração e o construtor.

Agora, se todas as suas operações fossem apenas chamadas de método, como:

double distance = pntA.distanceFrom(pntB);

então você está bem. Mas e se você usasse os membros explicitamente? Comparar

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

No primeiro caso, o código não será compilado. Você verá o erro imediatamente e poderá corrigi-lo. Mas se você tiver os mesmos nomes de membro, o erro será apenas no nível lógico, muito mais difícil de detectar.

Se você escreve em uma linguagem não orientada a objetos, é ainda mais fácil passar uma estrutura incorreta para a função. O que impede você de escrever o seguinte código?

double distance = calculate_distance_polar(cartesianPointA, polarPointB);

Diferentes tipos de dados, por outro lado, permitiriam encontrar o erro durante a compilação.

IMil
fonte