Pergunta

Estou aprendendo C ++ e programação. Estou criando uma classe chamada Distance. Quero permitir que o usuário (programador usando), minha classe a capacidade de converter distâncias a partir de uma unidade de medida para outra. Por exemplo: polegadas -> centímetros, milhas -> quilómetros, etc ...

O meu problema é que eu quero ter um método chamado ConvertTo que irá converter a qualquer unidade de medida.

Aqui está o que eu tenho até agora:

// unit_of_measure is an enum containg all my supported lengths,
// (eg. inches, centimeters, etc...)
int Distance::ConvertTo(unit_of_measure convert_unit)
{
    switch (convert_unit)
    {
        case inches:
            if (unit != inches) {
                if (unit == centimeters) {
                    distance *= CM_TO_IN;
                    unit = inches;
                    return 0;
                } else {
                    cerr << "Conversion not possible (yet)." << endl;
                    return 1;
                }
            } else {
                cout << "Warning: Trying to convert inches to inches." << endl;
                return 2;
            }
        case centimeters:
            if (unit != centimeters) {
                if (unit == inches) {
                    distance /= CM_TO_IN;
                    unit = centimeters;
                    return 0;
                } else {
                    cerr << "Conversion not possible (yet)." << endl;
                    return 1;
                }
            } else {
                cout << "Warning: Trying to convert inches to inches." << endl;
                return 2;
            }
// I haven't written anything past here yet because it seems
// like a bad idea to keep going with this huge switch 
// statement.
        default:
            cerr << "Undefined conversion unit." << endl;
            return -1;
    }
}

Então, o que eu faço? Devo quebrar este acima ou apenas continuar com o que vai tornar-se um ENORME instrução switch.

Foi útil?

Solução

dividi-lo em funções. O que você tem aí vai ser muito difícil manter e de usar. Seria mais conveniente para o usuário eo programador ter funções com nomes descritivos como:

double inchesToCentimeters(double inches);
double centimetersToInches(double cent);

Os nomes das funções dizer exatamente o que função a ser chamada, e não há necessidade de passar no parâmetro extra que mantém o controle das unidades.

Apenas como um aparte, a fim de evitar ter que manter o controle do que unidades de medição é em, é uma boa prática para armazenar sempre que você números em uma unidade comum em todos os lugares em seu programa, em seguida, converter para as unidades de exibição somente quando você precisa. Por exemplo, um programa que eu estou mantendo agora mantém todos os valores de distância em metros, mas pode converter para praticamente qualquer unidade de distância que você poderia pensar.

Quando você usa uma unidade comum, você salvar um monte de função de gravação. Diga a sua unidade de distância comum é metros, agora uma vez que você escrever as funções de conversão de metros para todas as outras unidades que você precisa, e de todas as outras unidades de metros, você pode combiná-las para ir de qualquer unidade - a metros - para qualquer outra unidade .

Outras dicas

A minha abordagem seria a de sempre guardar a distância na mesma unidade. Isso evita que você sempre ter que verificar suas unidades quando você precisa converter o valor.

Um esqueleto do código pode ser algo como isto:

class Distance
{
public:

   float ConvertTo (unit_of_measure convert_unit)
   {
      return (_distanceInInches * getConversionFactor(convert_unit));
   }

   float SetValue (unit_of_measure unit, float value)
   {
      _distanceInInches = (value / getConversionFactor(unit));
   }

private:   

   float getConversionFactor(unit_of_measure unit)
   {
      switch(unit)
      {
         // add each conversion factor here
      }
   }

   float _distanceInInches;
}

Se você não se importa uma dependência, o uso Boost.Units

Se você quiser manter exatamente a sua API atual, mas simplificar a sua aplicação, por que não representam sua unidade em termos de algum padrão arbitrário (por exemplo, 1 metro). No mínimo, em vez de ter ^ 2 (origem-> dest) possibilidades N, ter 2 * N (origem-> std) (STD> dest) conversões.

struct distance_unit {
   char const* name;
   double meters_per_unit;
   distance_unit() : name("meters"),meters_per_unit(1.) {}
   double to_meters(double in_units) { return in_units/meters_per_unit; }
   double to_units(double in_meters) { return in_meters*meters_per_unit; }
};

struct distance {
   double d;
   distance_unit unit;
   distance(double d,distance_unit const& unit) : d(d),unit(unit) {}
   distance(double meters,distance_unit const& unit,bool _)
      : d(unit.to_units(meters)),unit(unit) {}
   distance convert_to(distance_unit const& to) {
        return distance(unit.to_meters(d),to,false);
   }
   friend inline std::ostream& operator<<(std::ostream &o) {
      return o << d << ' ' << unit.name;
   }
};

Claro, o único benefício para isso é que distâncias exatamente representáveis ??(em termos de sua unidade) não vai se tornar inexata. Se você não se preocupam com arredondamento e igualdade exata dos montantes, esta é mais sensível:

struct distance {
   double meters;
   distance_unit preferred_unit;
   distance(double d,distance_unit const& unit) 
     : meters(unit.to_meters(d)),preferred_unit(unit) {}
   distance(double meters,distance_unit const& unit,bool _)
     : meters(meters),preferred_unit(unit)
   distance convert_to(distance_unit const& to) { 
       return distance(meters,to,false);
   }
   friend inline std::ostream& operator<<(std::ostream &o) {
      return o << unit.to_units(meters) << ' ' << unit.name;
   }

};

Se você usar STL, Criar um Mapa de Mapa de constante conversão. Então você pode obter a constante de conversão de "de" e "para".

Algo parecido com isto:

std::map <unit_of_measure, std::map<unit_of_measure, double>> ConversionConstants_FromTo;

ConversionConstants_FromTo(inches)(centimeters) = ...;
ConversionConstants_FromTo(inches)(miles)       = ...;

int Distance::ConvertTo(unit_of_measure convert_unit) {
    return distance*ConversionConstants_FromTo(unit, convert_unit)
}

Há dois níveis de análise que eu iria realizar.

Em primeiro lugar a interface que você oferece para o chamador. O chamador cria um objeto Distância com uma unidade específica, em seguida, o método de conversão muda a unidade e correspondendo à distância, os códigos de erro indicam o sucesso ou não. Presumivelmente, você também tem getters para aceess a unidade atual e distância corrsponding.

Agora, eu não gosto de uma interface tal stateful. Por que não uma interface como

 Distance {

      Distance(unit, value) { // constructor

      float getValue(unit) throws UnsupportedUnitException;
 }

Portanto, não há necessidade de que o chamador tem alguma idéia das unidades internas da distância. Nenhum comportamento stateful.

Em seguida, a instrução switch é claramente repetitivo. que deve ser um candidato para alguns refatoração.

Cada conversão pode ser expressa como a multiplicação. Você pode ter uma mesa, mantendo todos os factores de conversão que você suporta. Então você tem

       float getConversionFactor(fromUnit, toUnit) throws UnsupportedUnitException

que faz a pesquisa do fator de conversão, e depois aplicá-lo em sua getValue () método

       getValue(requestedUnit) {
             return value * getConversionfactor(myUnit, requestedUnit);
       }

Enquanto você ainda está aprendendo, pode valer a pena abandonar o uso da abordagem interruptor e enum em favor de uma família de estruturas, uma por unidade tratada, cada um contendo o valor de entrada e um tipo de tag única para torná-los diferentes . Isto tem algumas vantagens:

  1. conversões pode ser feito por sobrecargas de uma função com um nome comum ( "Convert" talvez?) Que torna a vida mais fácil se você quer fazer conversões em código templated.
  2. os meios sistema de tipo que você nunca acidentalmente converter galões de anos-luz como você não iria escrever uma sobrecarga o compilador poderia corresponder às conversões dimensionalmente inadequados.
  3. tempo de execução de um switch ou aninhada se os blocos depende do número de cláusulas, a abordagem sobrecarga é ligado em tempo de compilação

A desvantagem seria a sobrecarga de criar essa família de classes de tag ea necessidade de envolver seus argumentos na classe (struct mais provável) apropriado. Uma vez envolto você não teria os operadores numéricos habituais a menos que você escreveu-los.

A pena técnica de aprendizagem que imo.

Aqui estão mais duas coisas para pensar desde já há algumas idéias muito boas que flutuam em torno aqui.

(1) Se você não está indo para representar comprimentos como tipos de valor, então eu iria usar um espaço de nomes de funções livres em vez de uma classe. Esta é mais uma coisa de estilo que eu gosto de evangelizar -. se você não tem estado ou está pensando em métodos static, basta usar um namespace

namespace Convert {
  double inchesToCentimeters(double inches) { ... }
  double inchesToMeters(double inches) { ... }
} // end Convert namespace

(2) Se você estiver indo para usar um tipo de valor, em vez (que é o que eu recomendaria), então considerar (o que venho chamando) "construtores chamado" em vez de uma enumeração unidade bem como uma representação única unidade.

class Convert {
public:
  static Convert fromInches(double inches) {
      return Convert(inches * 0.0254);
  }
  static Convert fromCentimeters(double cm) {
      return Convert(cm / 100.0);
  }
  ...
  double toInches() const { return meters * 39.370079; }
  double toCentimeters() const { return meters * 100.0; }
  ...
protected:
  Convert(double meters_): meters(meters_) {}
private:
  double meters;
};

Isso fará com que o código de utilizador-terra muito legível e você pode colher os benefícios de escolher o que quer unidade interna facilita a sua vida também.

métodos separados são mais fáceis de ler no código fonte, mas quando a unidade alvo vem por exemplo a partir de uma seleção de usuário, você precisa de uma função onde você pode especificá-lo como parâmetro.

Em vez da instrução switch, você pode usar uma tabela de fatores de escala, por exemplo, entre a unidade específica e unidade SI.

Além disso, ter um olhar para Boost.Units , não exatamente resolver o seu problema, mas é perto o suficiente para ser interessante.

Eu combinaria NawaMan ea resposta de ph0enix. Em vez de ter um mapa de mapas, só tem 2 mapas completos de constantes. Um mapa contém conversão de metros, o outro mapa contém o inverso. Então, a função é algo como (em pseudocódigo):

function convertTo (baseUnitName, destinationUnitName) {
    let A = getInverseConstant(baseUnitName);
    let B = getConstant(destinationUnitName);

    return this.baseUnit*A*B;
}

O que é consideravelmente menor do que a sua instrução switch interruptor montanhosa, e dois mapas cheio de constantes são consideravelmente mais fácil de manter do que uma instrução switch, eu acho. Um mapa de mapas seria, essencialmente, ser apenas uma mesa de vezes, então por que não apenas armazenar os cooeficients verticais e horizontais em vez de um n * m bloco de memória.

Você pode codificar até escrever para ler as constantes a partir de um arquivo de texto, e em seguida, gerar as constantes inversas com um 1 / x em cada valor.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top