(dźwięk wybierania połączenia)
– Halo – słychać znudzony głos operatora
– Dzień dobry, w przyszłym tygodniu będziemy wylewać przyczółek wiaduktu na tej nowej obwodnicy, będziemy potrzebować tak z 80 m3 betonu – mówi rozentuzjazmowany praktykant
– Dobrze, a jakiego betonu potrzebujecie?
– To będzie mieszanka o fck = 40, fcm = 48, fctk,0,05 =2.5, Ecm = 35 …
– Panie, ocipiał Pan? Powiedz Pan jaka ma być klasa betonu.
– Aaa, o to chodzi. C40/50 … ale to Panu wystarczy?
– Mieszam te kamyki z cementem od 32 lat, wiem co to znaczy C40/50. Jestem jak fabryka. Niech Pan poda dokładny adres dostawy. A i połowa płatności z góry …
W poprzednim poście stworzyliśmy klasę Concrete, odpowiadającą inżynierskiemu pojęciu klasa betonu.
public class Concrete { public string Name { get; set; } public double ElasticModulus { get; set; } public Concrete(string name, double elasticModulus) { Name = name; ElasticModulus = elasticModulus; } public double CalculateEffectiveElasticModulus(double creepCoefficient) { return ElasticModulus / (1 + creepCoefficient); } }
Konstruktor tej klasy wymaga podania nazwy klasy betonu oraz modułu Younga. Tylko, że w 99% przypadków wartość modułu Younga jest skorelowana jeden-do-jeden z nazwą klasy betonu. Podobnie kolejne parametry opisujące zachowanie fizyczne betonu – wytrzymałość na ściskanie, rozciąganie, itd. Rozwijając nasz program o kolejne funkcjonalności wymagające sprecyzowania parametrów betonu szybko doszlibyśmy do konstruktora wymagającego >10 różnych wartości. Tworzenie takich obiektów (czysto ludzko) jest koszmarem – bardzo łatwo jest pomylić kolejnosć argumentów. A i tak te wszystkie parametry wynikają z zadanej klasy betonu – dokładnie jak w rozmowie z operatorem węzła betoniarskiego.
Dlatego proponuję wykorzystać inżynierskie zależności z Eurokodu i zmodyfikować naszą klasę Concrete. Spójrz co naklepałem:
public class Concrete { public string Name { get; private set; } public double Fck { get; private set; } public double Fck_cube { get; private set; } public double Fcm { get; private set; } public double Fctm { get; private set; } public double ElasticModulus { get; private set; } public Concrete(string name) { Name = name; InitalizeConcreteProperties(); } private void InitalizeConcreteProperties() { switch (Name) { case "C12/15": Fck = 12 * Math.Pow(10, 6); Fck_cube = 15 * Math.Pow(10, 6); break; default: throw new ArgumentNullException(nameof(Name), "Provided concrete class name is not allowed"); } Fcm = Fck + 8 * Math.Pow(10, 6); Fctm = (Fck <= 50 * Math.Pow(10, 6)) ? 0.30 * Math.Pow(Fck * Math.Pow(10, -6), 2 / 3.0) : 2.12 * Math.Log(1 + Fcm * Math.Pow(10, -6) / 10); ElasticModulus = 22 * Math.Pow((Fcm * Math.Pow(10, -6)) / 10, 0.3) * Math.Pow(10, 9); } //https://chodor-projekt.net/encyclopedia/pelzanie-skurcz-betonu/#(33) public double CalculateEffectiveElasticModulus(double creepCoefficient) { return ElasticModulus / (1 + creepCoefficient); } }
Co najbardziej rzuca Ci się w oczy? Mnie strasznie kłują te mnożenia przez Math.Pow(10, 6) i odwrotne Math.Pow(10, -6). Normowe wzory w większości wymagają podawania wartości w określonych jednostkach – nie wystarczy rzucić im wartości w paskalach. Czasami wzór został tak skonstruowany, że tylko podanie wartości w MPa da oczekiwany rezultat.
Tak było, jest i będzie – musimy się dostosować. Klasycznym rozwiązaniem byłoby przyjęcie, że operować będziemy na układzie SI i każdorazowo będziemy zamieniać podstawowe wartości na ich wersje z kilo/mega/giga – patrz przykład powyżej. Do tego piękny opis poszczególnych argumentów metod połączony ze wskazaniem oczekiwanej jednostki i mamy czyste ręce. Tylko, że prędzej czy później pomylimy się, podamy gdzieś formę w Pa zamiast w MPa, metody zaczną zwracać bzdurne wyniki. Samo patrzenie na taki kod powoduje, że podskórnie czujesz, że gdzieś tam czai się babol. Aż chciałoby się mieć magiczną różdżkę, która będzie na zawołanie dokonywać konwersji pomiędzy poszczególnymi jednostkami.
Zacznijmy więc tworzyć odpowiednie klasy potrafiące zamieniać … Wait, daj sobie na wstrzymanie. Zanim zaczniesz klepać nową funkcjonalność spójrz z boku na swój problem i zadaj fundamentalne pytanie: czy to ja jestem taki wyjątkowy czy jednak z tym zagadnieniem ktoś wcześniej się zetknął i może go rozwiązął? Odpowiedzi będą różne, ale warto pytać, zwłaszcza samego siebie.
Problem konwersji jednostek jest powszechnie znany, a co najlepsze – dostępne jest rozwiązanie, które weźmiemy z półki i wstrzykniemy do naszego programu. Przy kasie zobaczymy kwotę 0 zł – skorzystamy z biblioteki na licencji MIT, która umożliwia, wspiera dalsze wykorzystanie efektu pracy innych programistów. Pora poznać UnitsNet.
Ok, link wygląda na pierwszy rzut oka strasznie. Nie musisz analizować jak zbudowana jest ta biblioteka, jak ją skompilować – chcemy ją szybko i bezboleśnie wykorzystać w naszym programie. W tym celu naciśnij PPM na References i wybierz Manage NuGet Packages.
Witaj w królestwie paczek, które czekają na skosztowanie w Twoich kolejnych projektach. Chcemy spróbować paczki UnitsNet, więc wpisz proszę tę nazwę w polu wyszukiwania będąc w zakładce Browse. Po wybraniu paczki wybierz Install dostępne po prawej stronie.
Po zakończeniu procesu instalacji paczka jest gotowa do używania. W tym celu musimy rozszerzyć zakres poszukiwania klas dodając nową przestrzeń nazw, w tym wypadku using UnitsNet; Spójrz jak teraz możemy zapisać szkielet klasy Concrete:
using System; using UnitsNet; namespace ConcreteLibrary { public class Concrete { public string Name { get; private set; } public Pressure Fck { get; private set; } public Pressure Fck_cube { get; private set; } public Pressure Fcm { get; private set; } public Pressure Fctm { get; private set; } public Pressure ElasticModulus { get; private set; } public Concrete(string name) { Name = name; InitalizeConcreteProperties(); } private void InitalizeConcreteProperties() { switch (Name) { case "C12/15": Fck = Pressure.FromMegapascals(12); Fck_cube = Pressure.FromMegapascals(15); break; default: throw new ArgumentNullException(nameof(Name), "Provided concrete class name is not allowed"); } Fcm = Fck + Pressure.FromMegapascals(8); Fctm = Pressure.FromMegapascals( (Fck.Megapascals <= 50) ? 0.30 * Math.Pow(Fck.Megapascals, 2 / 3.0) : 2.12 * Math.Log(1 + Fcm.Megapascals / 10)); ElasticModulus = Pressure.FromGigapascals(22 * Math.Pow(Fcm.Megapascals / 10, 0.3)); } //https://chodor-projekt.net/encyclopedia/pelzanie-skurcz-betonu/#(33) public double CalculateEffectiveElasticModulus(double creepCoefficient) { return ElasticModulus.Pascals / (1 + creepCoefficient); } } }
Zniknęły magiczne zamiany z Pa na MPa i na odwrót. Zamiast mnożenia przez wielokrotności 10-ciu mamy zgrabną definicję:
Pressure Fck = Pressure.FromMegapascals(12);
Patrząc na to od razu czujesz co się dzieje w danej linijce. Podobnie jak wskazanie w jakiej jednostce chcesz uzyskać wartość, o patrz:
double valueInMegapascals = Fck.Megapascals;
Życie stało się zdecydowanie prostsze. Jeszcze jeżeli dodamy odczytywanie podstawowych wytrzymałości na ściskanie na bazie nazwy podanej przez użytkownika to całość klasy betonu zamknie się w poniższym kodzie:
using System; using System.Linq; using UnitsNet; namespace ConcreteLibrary { public class Concrete { public string Name { get; private set; } public Pressure Fck { get; private set; } public Pressure Fck_cube { get; private set; } public Pressure Fcm { get; private set; } public Pressure Fctm { get; private set; } public Pressure ElasticModulus { get; private set; } public Concrete(string name) { Name = name; InitalizeConcreteProperties(); } private void InitalizeConcreteProperties() { bool initialized = false; if (Name.StartsWith("C") && Name.Count(x => x == '/') == 1) { string[] splitted = Name.Split('/'); int fckFromName; int fckCubeFromName; if (int.TryParse(splitted[0].Substring(1), out fckFromName) && int.TryParse(splitted[1], out fckCubeFromName)) { Fck = Pressure.FromMegapascals(fckFromName); Fck_cube = Pressure.FromMegapascals(fckCubeFromName); initialized = true; } } if (!initialized) { throw new ArgumentNullException(nameof(Name), "Provided concrete class name is not allowed"); } Fcm = Fck + Pressure.FromMegapascals(8); Fctm = Pressure.FromMegapascals( (Fck.Megapascals <= 50) ? 0.30 * Math.Pow(Fck.Megapascals, 2.0 / 3.0) : 2.12 * Math.Log(1 + Fcm.Megapascals / 10.0)); ElasticModulus = Pressure.FromGigapascals(22 * Math.Pow(Fcm.Megapascals / 10.0, 0.3)); } //https://chodor-projekt.net/encyclopedia/pelzanie-skurcz-betonu/#(33) public double CalculateEffectiveElasticModulus(double creepCoefficient) { return ElasticModulus.Pascals / (1 + creepCoefficient); } } }
Linijki od 26 do 40 to zamiana nazwy klasy betonu na dwie podstawowe charakterystyki, które później przy pomocy normowych zależności zamieniane są na pozostałe zmienne opisujące nasz beton.
Z tego posta chcę żebyś zapamiętał jedną podstawową różnicę pomiędzy światem inżynierów a programistów – w tym drugim dzielenie się wiedzą jest standardem, również w postaci udostępniania za darmo wartościowych paczek, z których możesz skorzystać. Wzajemna pomoc nakręca rozwój – dopóki do gry nie włączy się dział finansów, ale to opowieść na inną historię.