TL; DR - J'essaie de concevoir une structure de données optimale pour définir des unités dans une unité de mesure.
UNE Unit of measure
est essentiellement un value
(ou une quantité) associée à un unit
. nités SI Avoir sept bases ou dimensions. Nommément: longueur, masse, temps, courant électrique, température, quantité de substance (moles) et intensité lumineuse.
Cela serait assez simple, mais il existe un certain nombre d'unités dérivées ainsi que des taux que nous utilisons fréquemment. Un exemple d'une unité combinée serait le Newton: kg * m / s^2
et un exemple de tarif serait tons / hr
.
Nous avons une application qui dépend fortement des unités implicites. Nous intégrerons les unités dans le nom de la variable ou de la colonne. Mais cela crée des problèmes lorsque nous devons spécifier une unité de mesure avec différentes unités. Oui, nous pouvons convertir les valeurs à l'entrée et à l'affichage, mais cela génère beaucoup de code temporel que nous aimerions encapsuler dans sa propre classe.
Il existe un certain nombre de solutions sur CodePlex et autres environnements collaboratifs. La licence pour les projets est acceptable, mais le projet lui-même finit généralement d'être trop léger ou trop lourd. Nous poursuivons notre propre licorne de "juste juste".
Idéalement, je pourrais définir une nouvelle unité de mesure en utilisant quelque chose comme ceci:
Uom myuom1 = nouvel UOM (10, volts);
[.____] uom myuom2 = nouvel UOM (43,2, newtons);
Bien sûr, nous utilisons un mélange d'unités impériales et SI basées sur les besoins de nos clients.
Nous devons également conserver cette structure des unités synchronisées avec une future table de base de données afin que nous puissions également fournir le même degré de cohérence dans nos données.
Quelle est la meilleure façon de définir les unités, les unités dérivées et les tarifs que nous devons utiliser pour créer notre classe de mesure de mesure? Je pouvais voir utiliser un ou plusieurs énumérums, mais cela pourrait être frustrant pour d'autres développeurs. Un seul énum serait énorme avec plus de 200 entrées, tandis que plusieurs Enums pourraient être déroutants en fonction des unités IMPERIAL SI VS et une ventilation supplémentaire basée sur la catégorisation de l'unité elle-même.
Exemples énormes montrant certaines de mes préoccupations:
myunits.volt
[.____] myunits.newton
[.____] Myunits.MeterSiunit.mètre
[.____] Impunit.foot drvdunit.newton
[.____] drvdunitsi.newton
Drvdunitip.ftlbs
Notre ensemble d'unités utilisées est assez bien définie et c'est un espace fini. Nous avons besoin de la capacité d'élargir et d'ajouter de nouvelles unités ou tarifs dérivés lorsque nous avons une demande de client pour eux. Le projet est en C # bien que je pense que les aspects de conception plus larges sont applicables à plusieurs langues.
L'une des bibliothèques que j'ai examinée permet une entrée de formulaire libre d'unités via une chaîne. Leur classe UOM a ensuite analysé la chaîne et les choses fendues en conséquence. Le défi avec cette approche est que cela oblige le développeur à penser et à rappeler ce que sont les formats de chaîne appropriés. Et j'exécute le risque d'une erreur/exception d'exécution si nous n'ajoutons pas de vérification supplémentaires dans le code pour valider les chaînes passées dans le constructeur.
Une autre bibliothèque a essentiellement créé trop de classes que le développeur devra travailler avec. Avec un UOM équivalent, il a fourni un DerivedUnit
et RateUnit
et ainsi de suite. Essentiellement, le code était trop complexe pour les problèmes que nous résolvons. Cette bibliothèque permettrait essentiellement de: toutes les combinaisons (qui sont légitimes dans le monde des unités) mais nous sommes heureux de mettre en place notre problème (simplifier notre code) en ne permettant pas toutes les combinées possibles.
D'autres bibliothèques étaient ridiculement simples et n'avaient même pas envisagé la surcharge de l'opérateur par exemple.
De plus, je ne suis pas aussi inquiet pour les tentatives de conversions incorrectes (par exemple: volts à mètres). Les Devs sont les seuls à avoir accès à ce niveau à ce stade et nous n'avons pas nécessairement besoin de protéger contre ces types d'erreurs.
Les bibliothèques de boost pour C++ comprennent un article sur - analyse dimensionnelle qui présente une mise en œuvre d'un échantillon d'unités de manutention de mesure.
Pour résumer: des unités de mesure sont représentées comme des vecteurs, chaque élément du vecteur représentant une dimension fondamentale:
typedef int dimension[7]; // m l t ...
dimension const mass = {1, 0, 0, 0, 0, 0, 0};
dimension const length = {0, 1, 0, 0, 0, 0, 0};
dimension const time = {0, 0, 1, 0, 0, 0, 0};
Les unités dérivées sont des combinaisons de ceux-ci. Par exemple, la force (masse * distance/heure ^ 2) serait représentée comme
dimension const force = {1, 1, -2, 0, 0, 0, 0};
Les unités impériales versus SI pourraient être traitées en ajoutant un facteur de conversion.
Cette implémentation s'appuie sur des techniques spécifiques C++ (à l'aide de la métaprogrammation de gabarit pour transformer facilement des unités de mesure différentes en différents types de temps de compilation), mais les concepts doivent transférer vers d'autres langages de programmation.
Je viens de relâcher des unités.net sur github et sur Nuget .
Cela vous donne toutes les unités et conversions communes. Il est léger, un appareil testé et supporte PCL.
Vers votre question:
Je n'ai pas encore vu le Saint Graal de solutions dans ce domaine. Comme vous l'énoncez, cela peut facilement devenir trop complexe ou trop verbeux pour travailler. Parfois, il est préférable de garder des choses simples et de mes besoins, cette approche s'est avérée suffisante.
Conversion explicite
Length meter = Length.FromMeters(1);
double cm = meter.Centimeters; // 100
double yards = meter.Yards; // 1.09361
double feet = meter.Feet; // 3.28084
double inches = meter.Inches; // 39.3701
Pressure p = Pressure.FromPascal(1);
double kpa = p.KiloPascals; // 1000
double bar = p.Bars; // 1 × 10-5
double atm = p.Atmosphere; // 9.86923267 × 10-6
double psi = p.Psi; // 1.45037738 × 10-4
Conversion dynamique
// Explicitly
double m = UnitConverter.Convert(1, Unit.Kilometer, Unit.Meter); // 1000
double mi = UnitConverter.Convert(1, Unit.Kilometer, Unit.Mile); // 0.621371
double yds = UnitConverter.Convert(1, Unit.Meter, Unit.Yard); // 1.09361
// Or implicitly.
UnitValue val = GetUnknownValueAndUnit();
// Returns false if conversion was not possible.
double cm;
val.TryConvert(LengthUnit.Centimeter, out cm);
Sur la base du fait que toutes les conversions requises sont des conversions d'échelle (sauf si vous devez prendre en charge les conversions de température. Calculs où la conversion implique un décalage est significativement plus complexe), je concevrais mon système "unité de mesure" comme celui-ci:
Une classe unit
contenant un facteur de mise à l'échelle, une chaîne pour la représentation textuelle de l'unité et une référence que la échelle unit
à. La représentation textuelle est présente à des fins d'affichage et la référence à l'unité de base pour savoir quelle unité le résultat est en train de faire des mathématiques sur des valeurs différentes avec différentes unités.
Pour chaque unité prise en charge, une instance statique de la classe unit
est fournie.
Une classe UOM
contenant une valeur et une référence à la valeur unit
. La classe UOM
fournit des opérateurs surchargés pour ajouter/soustraire un autre UOM
et pour multiplier/diviser avec une valeur sans dimension.
Si l'addition/la soustraction est effectuée sur deux UOM
avec le même unit
, il est effectué directement. Sinon, les deux valeurs sont converties à leurs unités de base respectives et à l'ajout/soustrait. Le résultat est rapporté comme étant dans la base unit
.
L'utilisation serait comme
unit volts = new unit(1, "V"); // base-unit is self
unit Newtons = new unit(1, "N"); // base-unit is self
unit kiloNewtons = new unit(1000, "kN", Newtons);
//...
UOM myUom1 = new UOM(10, volts);
UOM myUom2 = new UOM(43.2, kiloNewtons);
À mesure que les opérations sur les unités incompatibles ne sont pas considérées comme une question, je n'ai pas essayé de faire le type de conception à cet égard. Il est possible d'ajouter une vérification d'exécution en vérifiant que deux unités se réfèrent à la même unité de base.
Pensez à ce que votre code fait et ce qu'il va permettre. Avoir une énumération simple avec toutes les unités possibles de cela me permet de faire quelque chose comme convertir des volts en mètres. Cela n'est évidemment pas valable à un humain, mais le logiciel sera volontiers volontiers.
J'ai fait quelque chose de vaguement similaire à celui-ci une fois, et ma mise en œuvre avait des classes de base abstraites (longueur, poids, etc.) qui ont tous mis en œuvre IUnitOfMeasure
. Chaque classe de bases de résumé définissait un type par défaut (classe Length
avait une implémentation par défaut de classe Meter
) qu'elle utiliserait pour tous les travaux de conversion. Par conséquent, IUnitOfMeasure
_ Mise en œuvre deux méthodes différentes, ToDefault(decimal)
et FromDefault(decimal)
.
Le nombre réel que je voulais envelopper était un type générique acceptant IUnitOfMeasure
comme argument générique. Dire quelque chose comme Measurement<Meter>(2.0)
vous donne la sécurité automatique de type. La mise en œuvre des conversions implicites appropriées et des méthodes mathématiques sur ces classes vous permet de faire des choses comme Measurement<Meter>(2.0) * Measurement<Inch>(12)
et de renvoyer un résultat dans le type par défaut (Meter
). Je n'ai jamais travaillé sur des unités dérivées comme Newtons; Je les ai simplement laissés comme kilogramme * mètre/seconde/seconde.
Je crois que la réponse réside dans Réponse de débordement de la pile de Mariovw à:
Exemple pratique Où Tuple peut être utilisé dans .NET 4-0?
Avec des tuples, vous pourriez facilement mettre en œuvre un dictionnaire bidimensionnel (ou N-dimensionnel pour cette affaire). Par exemple, vous pouvez utiliser un tel dictionnaire pour implémenter une cartographie de change de devises:
var forex = new Dictionary<Tuple<string, string>, decimal>();
forex.Add(Tuple.Create("USD", "EUR"), 0.74850m); // 1 USD = 0.74850 EUR
forex.Add(Tuple.Create("USD", "GBP"), 0.64128m);
forex.Add(Tuple.Create("EUR", "USD"), 1.33635m);
forex.Add(Tuple.Create("EUR", "GBP"), 0.85677m);
forex.Add(Tuple.Create("GBP", "USD"), 1.55938m);
forex.Add(Tuple.Create("GBP", "EUR"), 1.16717m);
forex.Add(Tuple.Create("USD", "USD"), 1.00000m);
forex.Add(Tuple.Create("EUR", "EUR"), 1.00000m);
forex.Add(Tuple.Create("GBP", "GBP"), 1.00000m);
decimal result;
result = 35.0m * forex[Tuple.Create("USD", "EUR")]; // USD 35.00 = EUR 26.20
result = 35.0m * forex[Tuple.Create("EUR", "GBP")]; // EUR 35.00 = GBP 29.99
result = 35.0m * forex[Tuple.Create("GBP", "USD")]; // GBP 35.00 = USD 54.58
J'ai eu un besoin similaire pour ma demande. Tuple
est également immuable qui est également vrai d'objets tels que des poids et des mesures ... comme le dit "une pinte une livre le monde autour".
Mon code prototype: http://ideone.com/x7hz7i
Mes points de conception:
Longueur len = nouvelle longueur (); Len.Meters = 2.0; Console.writine (len.feet); [.____]
Longueur len = longueur.frommets (2.0);
Console.writeine (len.tostring ("ft")); Console.writine (len.tostring ("F15")); [.____] ("FTF15"));
[.____
[.____] // assez désordonné, buggy, dangereux, et pourrait ne pas être possible sans utiliser F # ou C++ mpl. [.____] // Il continue à dire que analyse dimensionnelle est non une fonctionnalité facultative pour UOM - [.____] // que vous l'utilisiez directement ou non. C'est requis .
Il y a un bon article dans un magazine sans objectif, il est en allemand: http://www.dotnetpro.de/articles/onlinearticle1398.aspx
L'idée de base est d'avoir une classe unitaire comme une longueur avec un basèmeasage. La classe contient le facteur de conversion, les surcharges de l'opérateur, les surcharges de totrage, l'analyseur des chaînes et une implémentation en tant qu'indexeur. Nous avons même mis en œuvre même la vue architective mais ce n'est pas libéré comme une bibliothèque.
public class Length : MeasurementBase
{
protected static double[] LengthFactors = { 1, 100, 1000, 0.001, 100 / 2.54 };
protected static string[] LengthSymbols = { "m", "cm", "mm", "km", "in" };
...
public virtual double this[Units unit]
{
get { return BaseValue * LengthFactors[(int)unit]; }
set { BaseValue = value / LengthFactors[(int)unit]; }
}
...
public static ForceDividedByLength operator *(Length length, Pressure pressure1)
{
return new ForceDividedByLength(pressure1[Pressure.Units.kNm2] * length[Units.m], ForceDividedByLength.Units.kNm);
}
...
Donc, vous voyez l'utilisation avec l'opérateur de pression ou simplement:
var l = new Length(5, Length.Units.m)
Area a = l * new Length("5 m");
a.ToString() // -> 25 m^2
double l2 = l[Length.Units.ft];
Mais comme tu l'as dit, je n'ai pas trouvé la licorne non plus :)