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 X
e Y
membros e outro com R
e Theta
membros.
Ou é demais e é melhor ter apenas uma estrutura com first
e second
como 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.
Respostas:
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.
fonte
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.
fonte
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()
eAngle()
(ou, eventualmente,Radius()
eTheta()
, dependendo).fonte
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
first
esecond
, 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
double
nã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
union
tipo em C para deixar sua intenção clara.fonte
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 seCoord
é 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 umstd::pair<double, double>
outuple
ou 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
Cartesian
e fornecer suporte para a conversãoPolar
paraCartesian
. Isso evita a implementação de versões diferentes de muitas operações.fonte
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.
fonte
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:
então você está bem. Mas e se você usasse os membros explicitamente? Comparar
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?
Diferentes tipos de dados, por outro lado, permitiriam encontrar o erro durante a compilação.
fonte