Qual é o melhor: um monte de getters ou 1 método com um parâmetro de string de seleção?


15

Nosso domínio do conhecimento envolve pessoas caminhando sobre uma placa de registro de pressão com os pés descalços. Fazemos reconhecimento de imagem que resulta em objetos da classe 'Foot', se um pé humano for reconhecido nos dados do sensor.

Existem vários cálculos que devem ser realizados nos dados do pé.

Agora, qual API seria melhor:

class Foot : public RecognizedObject  { 
  MaxPressureFrame getMaxPressureFrame();
  FootAxis getFootAxis();
  AnatomicalZones getAnatomicalZones();

  // + similar getters for other calculations

  // ...
}

Ou:

class Foot : public RecognizedObject {
  virtual CalculationBase getCalculation(QString aName);

  // ...
}

Agora, há muitos prós e contras com os quais eu posso pensar, mas não consigo decidir quais são os mais importantes. Observe que este é um aplicativo de usuário final, não uma biblioteca de software que vendemos.

Algum conselho?

Alguns profissionais da primeira abordagem podem ser:

  • BEIJO - tudo é muito concreto. A API, mas a implementação também.
  • valores de retorno fortemente digitados.
  • herdar dessa classe é à prova de idiotas. Nada pode ser substituído, apenas adicionado.
  • A API está muito fechada, nada entra, nada pode ser substituído, portanto, menos pode dar errado.

Alguns contras:

  • O número de getters aumentará, à medida que cada novo cálculo que inventamos for adicionado à lista
  • É mais provável que a API mude e, se forem introduzidas alterações mais recentes, precisamos de uma nova versão da API, o Foot2.
  • no caso de reutilização da classe em outros projetos, talvez não precisemos de todos os cálculos

Alguns profissionais da segunda abordagem:

  • mais flexível
  • é menos provável que a API mude (assumindo que a abstração esteja correta, caso contrário, a alteração custará mais)

Alguns contras:

  • digitado livremente. Precisa de transmissões em todas as chamadas.
  • o parâmetro string - tenho sentimentos ruins sobre isso (ramificação nos valores da string ...)
  • Não há nenhum caso / requisito de uso atual que exija flexibilidade extra, mas pode haver no futuro.
  • a API impõe restrições: todo cálculo precisa derivar de uma classe base. A obtenção de um cálculo será forçada através deste método 1, e a passagem de parâmetros extras será impossível, a menos que planejemos uma maneira ainda mais dinâmica e super flexível de passar parâmetros, o que aumenta ainda mais a complexidade.

5
Você pode criar enume ativar seus valores. Ainda assim, acho que a segunda opção é má, porque se desvia do KISS.
Vorac

É mais difícil encontrar todos os usos de um cálculo específico se você usar um parâmetro de sequência para acioná-lo. Difícil de ser 100%, você encontrou todos eles.
precisa saber é o seguinte

Pegue o melhor de dois mundos e escreva uma fachada com muitos getters invocando getCalculation().
nalply

1
Enums são definitivamente melhores que strings! Eu não tinha pensado nisso. Eles restringem a API e evitam o abuso do parâmetro string (como concat de tokens e outras porcarias). Então, acho que a comparação é entre a opção 1 e a opção 2 com enum em vez de string.
Bgie

Respostas:


6

Eu acho que na primeira abordagem vai valer a pena. As strings mágicas podem criar os seguintes problemas: Erros de digitação, uso indevido, segurança do tipo de retorno não trivial, falta de conclusão de código, código pouco claro (essa versão possui esse recurso? Acho que descobriremos em tempo de execução). O uso de enums resolverá alguns desses problemas, mas vamos analisar os contras que você levantou:

  • O número de getters aumentará, à medida que cada novo cálculo que inventamos for adicionado à lista

É verdade que isso pode ser irritante, mas mantém as coisas agradáveis ​​e rigorosas e fornece a conclusão do código em qualquer lugar do projeto em qualquer IDE moderno, com bons comentários de cabeçalho que são muito mais úteis que enumerações.

  • É mais provável que a API mude e, se forem introduzidas alterações mais recentes, precisamos de uma nova versão da API, o Foot2.

É verdade, mas esse é realmente um grande profissional;) ​​você pode definir interfaces para APIs parciais e não precisa recompilar a classe dependente que não é afetada pelas APIs mais recentes (portanto, não há necessidade do Foot2). Isso permite uma melhor dissociação, a dependência agora é da interface e não da implementação. Além disso, se uma interface existente for alterada, você terá um erro de compilação nas classes dependentes, o que é ótimo para evitar código obsoleto.

  • no caso de reutilização da classe em outros projetos, talvez não precisemos de todos os cálculos

Não vejo como o uso de seqüências de caracteres mágicas ou enumerações ajudará nisso ... Se eu entendi corretamente, você inclui o código na classe Foot ou o divide em algumas classes menores, e isso vale para as duas opções


Eu gosto da ideia de APIs parciais com interfaces, parece à prova de futuro. Eu irei com esse. Obrigado. Se ficar muito confuso (o pé implementa muitas interfaces), o uso de várias classes pequenas de adaptadores seria ainda mais flexível: se existirem várias variações de foot com diferentes APIs (como foot, humanfoot, dogfoot, humanfootVersion2), poderá haver um pequeno adaptador para cada para permitir que um widget GUI para trabalhar com todos eles ...
Bgie

Uma vantagem do uso de um seletor de comandos é que se pode ter uma implementação que recebe um comando que não entende como chamar um método auxiliar estático fornecido com a interface. Se o comando representa algo que pode ser feito com quase todas as implementações usando uma abordagem de uso geral, mas que algumas implementações podem ser capazes de fazer por melhores meios [considere, por exemplo IEnumerable<T>.Count], essa abordagem pode permitir que o código aproveite os benefícios de desempenho de novas recursos de interface ao usar implementações que os suportam, mas permaneçam compatíveis com implementações antigas.
Supercat

12

Eu recomendaria a opção 3: Deixe claro que os cálculos não são uma parte intrínseca da abstração de a Foot, mas operam nela. Em seguida, você pode dividir Foote os cálculos em classes separadas, assim:

class Foot : public RecognizedObject {
public:
    // Rather low-level API to access all characteristics that might be needed by a calculation
};

class MaxPressureFrame {
public:
    MaxPressureFrame(const Foot& aFoot); // Performs the calculation based on the information in aFoot
    //API for accessing the results of the calculation
};

// Similar classes for other calculations

Dessa forma, você ainda terá uma digitação forte do seu cálculo e poderá adicionar novos sem afetar o código existente (a menos que tenha cometido um erro grave na quantidade de informações expostas por Foot).


Definitivamente melhor do que as opções 1 e 2. Este é apenas um pequeno passo para fazer uso do padrão de design da estratégia.
Brian

4
Isso pode ser apropriado. Isso pode ser um exagero. Depende de quão complexos são os cálculos. Você adicionará um MaxPressureFrameStrategy e um MaxPressureFrameStrategyFactory? E tende a transformar o pé em um objeto anêmico.
precisa saber é o seguinte

Embora essa seja uma boa alternativa, reverter as dependências não funcionaria no nosso caso. O pé também tem que agir como algum tipo de mediador, pois alguns cálculos precisam ser recalculados se outro for alterado (devido ao usuário alterar um parâmetro ou mais).
Bgie

@Bgie: Com tais dependências entre os cálculos, eu concordo com @ user116462 que a primeira opção é best.`
Bart van Ingen Schenau
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.