TL; DR - Próbuję zaprojektować optymalną strukturę danych, aby zdefiniować jednostki w jednostce miary.
A Unit of measure
jest zasadniczo value
(lub ilością) związaną z unit
. Jednostki SI mają siedem zasad lub wymiarów. Mianowicie: długość, masa, czas, prąd elektryczny, temperatura, ilość substancji (mole) i natężenie światła.
Byłoby to dość proste, ale istnieje wiele pochodnych jednostek, a także stawki, których często używamy. Przykładową połączoną jednostką byłby Newton: kg * m / s^2
a przykładową stawką byłby tons / hr
.
Mamy aplikację, która w dużej mierze opiera się na domyślnych jednostkach. Osadzimy jednostki w nazwie zmiennej lub kolumny. Ale to stwarza problemy, gdy musimy określić jednostkę miary z różnymi jednostkami. Tak, możemy przekonwertować wartości na wejściu i na wyświetlaczu, ale generuje to dużo kodu napowietrznego, który chcielibyśmy zamknąć w swojej klasie.
Istnieje wiele rozwiązań dla codeplex i innych środowisk współpracy. Licencjonowanie projektów jest przyjemne, ale sam projekt zwykle okazuje się zbyt lekki lub zbyt ciężki. Ścigamy własnego jednorożca „w sam raz”.
Idealnie byłoby zdefiniować nową jednostkę miary, używając czegoś takiego:
UOM myUom1 = nowy UOM (10 woltów);
UOM myUom2 = nowy UOM (43,2, niutony);
Oczywiście używamy kombinacji jednostek Imperial i SI w zależności od potrzeb naszych klientów.
Musimy również zsynchronizować tę strukturę jednostek z przyszłą tabelą bazy danych, abyśmy mogli zapewnić ten sam stopień spójności również w naszych danych.
Jaki jest najlepszy sposób definiowania jednostek, jednostek pochodnych i szybkości, których musimy użyć, aby stworzyć naszą klasę jednostek miary? Widziałem użycie jednego lub więcej wyliczeń, ale może to być frustrujące dla innych programistów. Pojedynczy wylicznik byłby ogromny z ponad 200 wpisami, podczas gdy wielokrotne wyliczenia mogłyby być mylące w oparciu o SI względem jednostek imperialnych i dodatkowy podział oparty na kategoryzacji samej jednostki.
Przykłady enum pokazujące niektóre z moich obaw:
myUnits.Volt
myUnits.Newton
myUnits.meterSIUnit.meter
ImpUnit.foot DrvdUnit.Newton
DrvdUnitSI.Newton
DrvdUnitImp.FtLbs
Nasz zestaw używanych jednostek jest dość dobrze zdefiniowany i ma ograniczoną przestrzeń. Potrzebujemy możliwości rozszerzenia i dodania nowych jednostek pochodnych lub stawek, gdy mamy na nie zapotrzebowanie klientów. Projekt jest w języku C #, chociaż myślę, że szersze aspekty projektowania dotyczą wielu języków.
Jedna z bibliotek, które przeglądałem, umożliwia swobodne wprowadzanie jednostek za pomocą łańcucha. Następnie klasa UOM parsowała łańcuch i odpowiednio wstawiała różne elementy. Wyzwanie związane z tym podejściem polega na tym, że zmusza programistę do myślenia i zapamiętywania poprawnych formatów ciągów. I narażam się na błąd / wyjątek w czasie wykonywania, jeśli nie dodamy dodatkowych kontroli w kodzie, aby sprawdzić ciągi znaków przekazywane do konstruktora.
Inna biblioteka zasadniczo stworzyła zbyt wiele klas, z którymi programista musiałby pracować. Wraz z równoważną JM zapewnił DerivedUnit
a RateUnit
i tak dalej. Zasadniczo kod był zbyt skomplikowany w przypadku problemów, które rozwiązujemy. Ta biblioteka zasadniczo umożliwiałaby dowolne: dowolne kombinacje (co jest uzasadnione w świecie jednostek), ale chętnie rozszerzymy nasz problem (uprościć nasz kod), nie zezwalając na każdą możliwą kombinację.
Inne biblioteki były absurdalnie proste i nawet nie brały pod uwagę na przykład przeciążenia operatora.
Ponadto nie martwię się tak bardzo o próby niepoprawnej konwersji (na przykład: woltów na metry). Deweloperzy są jedynymi, którzy uzyskają dostęp na tym poziomie w tym momencie i niekoniecznie musimy chronić się przed tego rodzaju błędami.
Odpowiedzi:
Biblioteki Boost dla C ++ zawierają artykuł na temat analizy wymiarowej, który przedstawia przykładową implementację jednostek miary.
Podsumowując: Jednostki miary są reprezentowane jako wektory, przy czym każdy element wektora reprezentuje podstawowy wymiar:
Jednostki pochodne to ich kombinacje. Na przykład siła (masa * odległość / czas ^ 2) byłaby reprezentowana jako
Jednostki imperialne kontra jednostki SI można by obsłużyć, dodając współczynnik konwersji.
Ta implementacja opiera się na technikach specyficznych dla C ++ (przy użyciu metaprogramowania szablonów w celu łatwego przekształcenia różnych jednostek miar w różne typy czasu kompilacji), ale koncepcje powinny zostać przeniesione na inne języki programowania.
źródło
mpl::vector_c<int,1,0,0,0,0,0,0>
) Zamiast stałych; artykuł przedstawia najpierw podejście consts jako wyjaśnienie (i prawdopodobnie nie wyjaśniłem tego dobrze). Użycie consts działałoby jako alternatywa (straciłbyś trochę bezpieczeństwa podczas kompilacji). Korzystanie z przestrzeni nazw w celu uniknięcia zanieczyszczenia nazwy jest z pewnością opcją.Właśnie opublikowałem Units.NET na Github i NuGet .
Daje ci wszystkie wspólne jednostki i konwersje. Jest lekki, przetestowany jednostkowo i obsługuje PCL.
W kierunku twojego pytania:
Jeszcze nie widziałem świętego Graala rozwiązań w tej dziedzinie. Jak twierdzisz, może łatwo stać się zbyt skomplikowane lub zbyt szczegółowe, aby z nim pracować. Czasami najlepiej jest zachować prostotę i dla moich potrzeb takie podejście okazało się wystarczające.
Jawna konwersja
Dynamiczna konwersja
źródło
Truple<T1, T2, T3>(x, y, z)
Tuple
. Nie widzę twojejUnitConverter
klasy, ale wygląda na to, że IMO może mieć podobną funkcjonalność jakTuple
klasa.Jeśli możesz użyć przełączania na F # zamiast C #, F # ma system jednostek miary (zaimplementowany przy użyciu metadanych wartości), który wygląda tak, jakby pasował do tego, co próbujesz zrobić:
http://en.wikibooks.org/wiki/F_Sharp_Programming/Units_of_Measure
Szczególnie:
źródło
Important: Units of measure look like a data type, but they aren't. .NET's type system does not support the behaviors that units of measure have, such as being able to square, divide, or raise datatypes to powers. This functionality is provided by the F# static type checker at compile time, **but units are erased from compiled code**. Consequently, it is not possible to determine value's unit at runtime.
W oparciu o fakt, że wszystkie wymagane konwersje są konwersjami skalowanymi (z wyjątkiem sytuacji, gdy trzeba wesprzeć konwersje temperatury. Obliczenia, w których konwersja obejmuje przesunięcie, są znacznie bardziej złożone), zaprojektowałbym mój system „jednostki miary” w następujący sposób:
Klasa
unit
zawierająca współczynnik skalowania, ciąg znaków dla reprezentacji tekstowej jednostki i odwołanie, do któregounit
skaluje się. Reprezentacja tekstowa służy do celów wyświetlania, a odniesienie do jednostki podstawowej pozwala ustalić, w której jednostce jest wynik, wykonując matematykę na wartościach z różnymi jednostkami.Dla każdej obsługiwanej jednostki
unit
zapewniona jest statyczna instancja klasy.Klasa
UOM
zawierająca wartość i odwołanie do wartościunit
.UOM
Klasa dostarcza przeciążone operatory dodawania / odejmowania innyUOM
i do mnożenia / dzielenia o wartości bezwymiarowej.Jeśli dodawanie / odejmowanie jest wykonywane na dwóch
UOM
z tym samymunit
, jest wykonywane bezpośrednio. W przeciwnym razie obie wartości są konwertowane na odpowiednie jednostki podstawowe i dodawane / odejmowane. Wynik jest zgłaszany jako znajdujący się w bazieunit
.Wykorzystanie byłoby jak
Ponieważ operacje na niekompatybilnych urządzeniach nie są uważane za problem, nie próbowałem uczynić projektu bezpiecznym pod tym względem. Można dodać kontrolę czasu wykonywania, sprawdzając, czy dwie jednostki odnoszą się do tej samej jednostki podstawowej.
źródło
95F - 85F
? Co to jest20C - 15C
? W obu przykładach obaUOM
miałyby to samounit
. Czy odejmowania byłyby wykonywane bezpośrednio?10 F
i5 C
. Obliczenia są wykonywane, jeśli to możliwe, bezpośrednio, aby uniknąć niepotrzebnych konwersji. Dodanie metod konwersji jednostek byłoby dość trywialneUOM
, ale w przypadku konwersji Celsjusza-Fahrenheitaunit
klasa musiałaby zostać rozszerzona o możliwość przesunięcia oprócz współczynnika skalowania.95F - 85F
! =10F
.95F
o85F
? Według mojej wiedzy Fahrenheit jest nadal skalą liniową.20C - 15C = 5C
, to mówimy293.15K - 288.15K = 278.15K
, co jest oczywiście błędne.Zastanów się, co robi Twój kod i na co pozwoli. Posiadanie prostego wyliczenia ze wszystkimi możliwymi jednostkami pozwala mi na coś takiego, jak przeliczenie woltów na metry. To oczywiście nie dotyczy człowieka, ale oprogramowanie chętnie spróbuje.
Kiedyś zrobiłem coś nieco podobnego do tego, a moja implementacja miała abstrakcyjne klasy podstawowe (długość, waga itp.), Które wszystkie zaimplementowały
IUnitOfMeasure
. Każda klasa baz abstrakcyjnych zdefiniowała typ domyślny (klasaLength
miała domyślną implementację klasyMeter
), której używałaby do wszystkich prac związanych z konwersją. DlategoIUnitOfMeasure
wdrożono dwie różne metodyToDefault(decimal)
orazFromDefault(decimal)
.Rzeczywista liczba, którą chciałem zawinąć, to typ ogólny, który przyjmuje
IUnitOfMeasure
jako swój ogólny argument. Powiedzenie czegoś takiegoMeasurement<Meter>(2.0)
zapewnia automatyczne bezpieczeństwo typu. Wdrożenie poprawnych niejawnych konwersji i metod matematycznych na tych klasach pozwala wykonywać takie rzeczy jakMeasurement<Meter>(2.0) * Measurement<Inch>(12)
i zwracać wynik w domyślnym typie (Meter
). Nigdy nie opracowałem pochodnych jednostek, takich jak Newtony; Po prostu zostawiłem je jako kilogram * metr / sekundę / sekundę.źródło
Wierzę, że odpowiedź leży w odpowiedzi Przepełnienia stosu MarioVW na:
Miałem podobną potrzebę mojej aplikacji.
Tuple
jest również niezmienne, co dotyczy również przedmiotów takich jak ciężary i miary… Jak mówi przysłowie: „półtora funta za cały świat”.źródło
Mój prototypowy kod: http://ideone.com/x7hz7i
Moje punkty konstrukcyjne:
źródło
Jest dobry artykuł w czasopiśmie, który bez wątpienia jest w języku niemieckim: http://www.dotnetpro.de/articles/onlinearticle1398.aspx
Podstawową ideą jest posiadanie klasy Unit, takiej jak Length, z BaseMeasurement. Klasa zawiera współczynnik konwersji, przeciążenia operatora, przeciążenia ToString, parser ciągów oraz implementację jako indeksator. Wdrożyliśmy nawet pogląd Architekta, ale nie jest on udostępniany jako biblioteka.
Więc widzisz użycie z operatorem ciśnienia lub po prostu:
Ale jak powiedziałeś, nie znalazłem też jednorożca :)
źródło
Jest to racja bytu
units
komendy Unix , która robi to wszystko przy użyciu podejścia opartego na plikach danych do określania relacji.źródło
units
. Głównym powodem, dla którego jednostki nie będą działać w moim szerszym rozwiązaniu, są ciągi znaków w dowolnej formie. To prawda, że w zamian dostarcza komunikaty o błędach, ale to podejście jest skierowane do programistów, którzy zintegrują ten kod z naszą aplikacją. Ciągi w dowolnej formie stanowią zbyt dużą szansę na błąd.units
plik danych. Sposób, w jaki definiuje relacje między ilościami, jest bardzo czysty i może być przydatny dla twojego problemu.