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

49

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

Faz sentido criar duas estruturas diferentes para cada tipo de pontos, um com X e Y membros e um 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 mudará muito. Mas estou curioso para saber o que é melhor do ponto de vista do design.

Estou pensando que a primeira opção é melhor. Parece mais legível e eu terei o benefício da verificação de tipos.

    
por Mhd.Tahawi 10.11.2015 / 09:36
fonte

7 respostas

17

Eu vi as duas soluções, então é definitivamente dependente do contexto.

Por questões de legibilidade, 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 encontra duplicando o código, como as operações matriciais vetoriais *. Pode ser frustrante quando a operação em particular não está disponível no seu tipo de vetor, porque ninguém a portou.

A solução extrema (a qual finalmente cedeu) é ter uma classe base modelada de CRTP com funções get < 0 > () obter < 1 > e obter < 2 >) para obter os elementos de uma 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 chega a 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.

    
por 10.11.2015 / 18:18
fonte
114

Sim, faz muito sentido.

O valor de uma estrutura não é apenas o fato de encapsular os dados em um nome prático. O valor é que codifica suas intenções para que o compilador possa ajudá-lo a verificar se você não os viola algum dia (por exemplo, confundir uma coordenada polar definida para um conjunto de coordenadas cartesianas).

As pessoas são ruins em lembrar tais detalhes, mas são boas em criar planos ousados e inventivos. Computadores são bons em detalhes incômodos e ruins em planos criativos. Por isso, é sempre uma boa ideia transferir a maior quantidade de manutenção de detalhes para o computador, deixando sua mente livre para trabalhar no grande plano.

    
por 10.11.2015 / 09:40
fonte
18

Sim, enquanto o Cartesiano e o Polar são (em seu lugar) esquemas de representação de coordenadas eminentemente sensatos, 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, também pode valer a pena implementar uma interface do Coordinate, com métodos como X() , Y() , Displacement() e Angle() (ou possivelmente Radius() e Theta() , dependendo de) .

    
por 10.11.2015 / 11:15
fonte
8

No final, o objetivo da programação é alternar os bits do transistor para fazer 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 alto nível para ajudá-lo a esconder a complexidade.

Se você acabou de criar uma estrutura com membros chamados first e second , os nomes não significam nada; você essencialmente os trataria como endereços de memória. Isso anula o propósito da linguagem de programação de alto nível.

Além disso, só porque todos eles são representáveis como double não significa que você possa 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 em C para deixar sua intenção clara.

    
por 10.11.2015 / 21:41
fonte
2

Em primeiro lugar, tenha ambos explicitamente, conforme a resposta inteiramente correta de @ Kilian-foth.

No entanto, gostaria de adicionar:

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

Além disso, uma abordagem pode ser tratar um tipo de coordenada como mais fundamental e o outro como apenas suporte para interação com o usuário ou externo.

Assim, você pode garantir que todas as operações fundamentais sejam codificadas para Cartesian e, em seguida, fornecer suporte para converter Polar em Cartesian . Isso evita a implementação de diferentes versões de muitas operações.

    
por 10.11.2015 / 23:34
fonte
1

Uma possível solução, dependendo da linguagem e se você sabe 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, contanto que as classes sejam exatamente as mesmas, você pode manter apenas uma, mas assim que precisar alterá-las, não será necessário modificar o código usando-as, uma vez que os tipos já foi usado distintamente.

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

    
por 11.11.2015 / 12:28
fonte
0

Acredito que ter os mesmos nomes de membros é uma má ideia neste caso, porque torna o código mais propenso a erros.

Imagine o cenário: você tem alguns pontos cartesianos: pntA e pntB. Então você decide, por algum motivo, que eles devem ser melhor representados em coordenadas polares e alterar 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);

você está bem. Mas e se você usou 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 membros, o erro será apenas no nível lógico, muito mais difícil de detectar.

Se você escrever 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, permitirão que você encontre o erro durante a compilação.

    
por 11.11.2015 / 16:54
fonte

Tags