Soll ich eine Methode schreiben Entfernungen oder eine Reihe von Methoden zu konvertieren?

StackOverflow https://stackoverflow.com/questions/1408056

  •  05-07-2019
  •  | 
  •  

Frage

Ich lerne gerade C ++ und Programmierung. Ich erstelle eine Klasse namens Distance. Ich mag den Benutzer (Programmierer verwenden), meine Klasse der Fähigkeit zu konvertieren Entfernungen von einer Maßeinheit zu einem anderen ermöglichen. Zum Beispiel: Zoll -> Zentimeter, Meilen -> Kilometer, etc ...

Mein Problem ist, dass ich eine Methode namens ConvertTo haben will, die jeden Maßeinheit konvertieren.

Hier ist, was ich bisher:

// 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;
    }
}

Also, was soll ich tun? Soll ich diese brechen oder einfach nur weiter auf mit dem, was ich zu einer riesig switch-Anweisung.

War es hilfreich?

Lösung

Break it up in Funktionen. Was haben Sie es sehr schwierig sein wird, zu halten und zu verwenden. Es wäre bequemer für den Benutzer und die Programmierer Funktionen mit beschreibenden Namen haben wie:

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

Die Funktionsnamen sagen Ihnen genau, was zu nennen funktionieren, und es gibt keine Notwendigkeit, in den zusätzlichen Parameter zu übergeben, die den Überblick über die Einheiten hält.

So wie eine beiseite, um mit zu verhindern, um zu verfolgen, welchen Einheiten eine Messung ist, ist es eine gute Praxis, immer speichern Sie Nummern in einer gemeinsamen Einheit überall in Ihrem Programm, wandelt dann auf die Anzeigeeinheiten nur dann, wenn du musst. Zum Beispiel hält ein Programm, das ich beibehalten werde nun alle Distanzwerte in Metern, kann aber konvertieren über jede Entfernung Einheit nur könnte man denken.

Wenn Sie eine gemeinsame Einheit verwenden, können Sie sich eine Menge Funktion Schreiben speichern. Sagen Sie Ihre gemeinsame Abstand Einheit ist Meter, jetzt, wenn Sie die Funktionen schreiben von Metern zu jeder anderen Einheit konvertieren Sie brauchen, und von allen anderen Einheiten Meter, können Sie diese kombinieren von jeder Einheit zu gehen - zu Meter - zu jeder anderen Einheit .

Andere Tipps

würde Mein Ansatz sein, immer den Abstand zu speichern in der gleichen Einheit. Dadurch wird vermieden, Sie immer mit Ihren Einheiten doppelt überprüfen, wenn Sie den Wert konvertieren müssen.

Ein Skelett des Codes könnte wie folgt aussehen:

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;
}

Wenn Sie nicht eine Abhängigkeit ausmacht, verwenden Sie Boost.Units

Wenn Sie genau Ihre aktuelle API behalten mögen, aber ihre Umsetzung vereinfachen, warum Sie das Gerät nicht in Bezug auf einem beliebigen Standard darstellen (beispielsweise 1 Meter). Am allerwenigsten, anstelle von N ^ 2 (Quellen-> dest) Möglichkeiten, haben 2 * N (Quellen-> std) (STD-> dest) Konvertierungen.

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;
   }
};

Natürlich ist der einzige Vorteil dazu, dass genau darstellbare Entfernungen (in ihrer Einheit) nicht ungenauer werden. Wenn Sie nicht über Rundung und exakte Gleichheit der Summen ist es egal, das ist sinnvoller:

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;
   }

};

Wenn Sie STL verwenden, erstellen Karte von Karte von konstanter Conversion. So können Sie die Umrechnungskonstante von „von“ erhalten und „zu“.

So etwas wie folgt aus:

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)
}

Es gibt zwei Ebenen der Analyse würde ich durchführen.

Zuerst wird die Schnittstelle, die Sie mit dem Anrufer bieten. Der Anrufer schafft eine Distanz Objekt mit einer bestimmten Einheit, und dann ändert sich die convert-Methode das Gerät und die entsprechenden Abstand, Fehlercodes für den Erfolg zeigen oder auf andere Weise. Vermutlich auch Sie Getter haben die aktuelle Einheit und corrsponding Abstand aceess.

Nun weiß ich nicht, wie eine solche Stateful-Schnittstelle. Warum nicht eine Schnittstelle wie

 Distance {

      Distance(unit, value) { // constructor

      float getValue(unit) throws UnsupportedUnitException;
 }

So gibt es keine Notwendigkeit für den Anrufer ist eine Idee der internen Einheiten des Abstandes zu haben. Kein Stateful Verhalten.

Dann ist die switch-Anweisung eindeutig wiederholend. dass muss ein Kandidat für einige Refactoring sein.

Jede Umwandlung kann als Multiplikation ausgedrückt werden. Sie können eine Tabelle, die alle die Umwandlung halten Sie Unterstützung Faktoren. So haben Sie

       float getConversionFactor(fromUnit, toUnit) throws UnsupportedUnitException

, die die Suche nach dem Umrechnungsfaktor der Fall ist, und dann gilt es in der Methode getValue ()

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

Während Sie noch lernen, könnte es sich lohnen, die Verwendung des Schalters und Enum-Ansatz für eine Familie von Strukturen zu verlassen, eine pro Einheit behandelt, die jeweils den Eingangswert und einen eindeutigen Tag-Typ enthalten, sie anders zu machen . Dies hat einige Vorteile:

  1. Umwandlungen können durch Überlastung einer Funktion mit einem gemeinsamen Namen ( „Konvertieren“ vielleicht?), Die das Leben leichter macht getan werden, wenn Sie Konvertierungen in Templat-Code tun mögen.
  2. der Typ-System bedeutet, dass Sie versehentlich nie Gallonen Lichtjahre konvertieren, wie Sie nicht eine Überlastung der Compiler zu form unangemessenen Umwandlungen passen schreiben würde könnte.
  3. Ausführungszeit eines Schalters oder verschachtelt, wenn Blöcke ist abhängig von der Anzahl der Klauseln wird die Überlastung Ansatz bei der Kompilierung geschaltet

Der Nachteil wäre der Aufwand für diese Familie von Tag-Klassen zu schaffen und die Notwendigkeit, Ihre Argumente in der Klasse einpacken (struct wahrscheinlicher) angemessen. Einmal eingewickelt würden Sie nicht die üblichen numerischen Operatoren haben, wenn Sie sie geschrieben haben.

Eine Technik wert Lernen obwohl imo.

Hier sind zwei weitere Dinge zu denken, da es bereits schon einige sehr gute Ideen hier schwimmen.

(1) Wenn Sie nicht als Werttypen darstellen Längen gehen, dann würde ich statt einer Klasse einen Namensraum voller kostenloser Funktionen nutzen. Dies ist eher ein Stil, was ich evangelisieren mag -. , wenn Sie nicht Staat oder über static Methoden denken, verwenden Sie einfach einen Namespace

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

(2) Wenn Sie vorhaben, stattdessen einen Werttyp zu verwenden (das ist, was ich empfehlen würde), dann betrachten (was ich genannt haben) „genannt Konstrukteuren“ statt einer Einheit Enum sowie eine Einheit Darstellung.

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;
};

Dies wird Ihre Benutzer-Land Code sehr lesbar machen, und Sie können die Vorteile der Wahl, was interne Einheit macht Ihnen das Leben leicht ernten.

Separate Methoden sind einfacher im Quellcode zu lesen, aber wenn die Zieleinheit kommt beispiel von einer Benutzerauswahl, benötigen Sie eine Funktion, wo Sie es als Parameter angeben können.

Statt der switch-Anweisung, können Sie eine Tabelle verwenden Faktoren zum Beispiel Skalierung zwischen der spezifischen Einheit und SI-Einheit.

Auch einen Blick auf Boost.Units , es löst nicht genau Ihr Problem, aber es ist nahe genug, um interessant zu sein.

Ich würde kombinieren NawaMan und ph0enix Antwort. Anstatt eine Karte von Karten haben, hat nur 2 von Konstanten voll abbildet. Eine Karte enthält Umwandlung von Metern, die andere Karte, um die inverse enthält. Dann ist die Funktion ist so etwas wie (in Pseudo-Code):

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

    return this.baseUnit*A*B;
}

, die wesentlich kürzer als die bergige Schalter switch-Anweisung ist, und zwei Karten voller Konstanten sind wesentlich leichter zu pflegen als eine Switch-Anweisung, glaube ich. Eine Karte von Karten würden im Wesentlichen sein nur mal Tisch, also warum speichern nicht nur die vertikalen und horizontalen cooeficients anstelle eines n * m Speicherblock.

Sie könnten sogar Code schreiben, um die Konstanten aus einer Textdatei zu lesen, und erzeugen dann die inversen Konstanten mit einem 1 / x auf jedem Wert.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top