Работа С Разными Единицами Одного Типа И Их Преобразование

Добрый день.

Я столкнулся с интересной проблемой в большом проекте, где было много расчетов разных расстояний.

Несмотря на то, что данные собираются из разных источников, они могут быть в разных единицах измерения – здесь метры, там миллиметры.

Трудно за всем этим уследить, когда расчеты разбросаны повсюду.

А если переменная объявлена, то зачастую только автор подробно знает, в каких единицах она находится, поскольку комментариев в коде почти нет. А автор бросил/забыл/запил.

Это предлагает решение описывать каждую единицу как отдельный тип, например так:

  
  
  
  
   

type TSizeMeter = single; TSizeMilliMeter = single;

Таким образом мы повысили читаемость кода.

Но как защититься от ошибок конвертации? В конце концов, эти типы совместимы, и их приведение не вызовет никаких ошибок или предупреждений (см.

здесь ).

Я пока не нашел способа отображать предупреждение при приведении типов, но мы можем написать автоматическое преобразование, если такое приведение происходит. Вот простейший пример со сложением и вычитанием:

interface type TSizeMeter = record value:single; const units='m'; class operator Add(a, b: TSizeMeter): TSizeMeter; class operator Subtract(a, b: TSizeMeter): TSizeMeter; class operator Implicit(a: single): TSizeMeter; class operator Implicit(a: TSizeMeter): single; end; TSizeMiliMeter = record value:single; const units='mm'; class operator Add(a, b: TSizeMiliMeter): TSizeMiliMeter; class operator Subtract(a, b: TSizeMiliMeter): TSizeMiliMeter; class operator Implicit(a: single): TSizeMiliMeter; class operator Implicit(a: TSizeMiliMeter): single; class operator Implicit(a: TSizeMiliMeter): TSizeMeter; class operator Implicit(a: TSizeMeter): TSizeMiliMeter; end; implementation class operator TSizeMeter.Add(a, b: TSizeMeter): TSizeMeter; begin result.value:=a.value+b.value; end; class operator TSizeMeter.Subtract(a, b: TSizeMeter): TSizeMeter; begin result.value:=a.value-b.value; end; class operator TSizeMeter.Implicit(a: single): TSizeMeter; begin result.value:=a; end; class operator TSizeMeter.Implicit(a: TSizeMeter): single; begin result:=a.value; end; class operator TSizeMiliMeter.Add(a, b: TSizeMiliMeter): TSizeMiliMeter; begin result.value:=a.value+b.value; end; class operator TSizeMiliMeter.Subtract(a, b: TSizeMiliMeter): TSizeMiliMeter; begin result.value:=a.value-b.value; end; class operator TSizeMiliMeter.Implicit(a: single): TSizeMiliMeter; begin result.value:=a; end; class operator TSizeMiliMeter.Implicit(a: TSizeMiliMeter): single; begin result:=a.value; end; class operator TSizeMiliMeter.Implicit(a: TSizeMiliMeter): TSizeMeter; begin result.value:=a.value/1000; end; class operator TSizeMiliMeter.Implicit(a: TSizeMeter): TSizeMiliMeter; begin result.value:=a.value*1000; end;

И вот его использование:

var v1:TSizeMeter; v2:TSizeMiliMeter; v3:TSizeMeter; v4:TSizeMiliMeter; begin v1:=1.1; v2:=111.1; s1:=v1; s2:=v2; writeln(formatfloat('0.000',v1.value)+' '+v1.units+' or '+formatfloat('0.000',s1)); writeln(formatfloat('0.000',v2.value)+' '+v2.units+' or '+formatfloat('0.000',s2)); writeln('+'); v3:=v1+v2; v4:=v1+v2; writeln(formatfloat('0.000',v3.value)+' '+v3.units); writeln(formatfloat('0.000',v4.value)+' '+v4.units); writeln('-'); v3:=v1-v2; v4:=v1-v2; writeln(formatfloat('0.000',v3.value)+' '+v3.units); writeln(formatfloat('0.000',v4.value)+' '+v4.units); writeln('cast'); v3:=v2; v4:=v1; writeln(formatfloat('0.000',v3.value)+' '+v3.units); writeln(formatfloat('0.000',v4.value)+' '+v4.units); writeln('mix'); v3:=v2+22.22; s1:=v1+33.33; writeln(formatfloat('0.000',v3.value)+' '+v3.units); writeln(formatfloat('0.000',s1)); end.

Что даст следующий результат:

1100 м или 1100 111 100 мм или 111 100 + 1211 м 1211 100 мм − 0,989 м 988 900 мм бросать 0,111 м 1100 000 мм смешивание 0,133 м 34,430
Это решение не является идеальным, так как делает преобразование неочевидным, что может создать новые проблемы.

Но, если вы объявите все переменные правильного типа, проблем возникнуть не должно.

Более серьезное решение, которое ограничит приведение типов, — это вызвать исключение при попытке приведения, например:

class operator TSizeMiliMeter.Implicit(a: TSizeMiliMeter): TSizeMeter; begin raise Exception.Create('Typecast not allowed'); end; class operator TSizeMiliMeter.Implicit(a: TSizeMeter): TSizeMiliMeter; begin raise Exception.Create('Typecast not allowed'); end;

В этом случае мы получим ошибку в строке:

v3:=v1+v2;

Вы можете развивать решение дальше, создав свой собственный тип выполнения.

Если кто сталкивался с подобными проблемами, поделитесь опытом в комментариях :) Наверняка есть более изящные решения, чем описанные выше.

P.S: Тест проводился в Delphi 10.1. Теги: #delphi #приведение типов #delphi

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.