Конвертер единиц измерения: температура

Prev Next

To download the app, visit: [tulip](https://<span translate=).co/library/apps/unit-converter--temperature/">Library

В этой статье один из наших разработчиков библиотеки, Тамаш, подробно рассказывает о решениях, лежащих в основе приложения-строительного блока «Конвертер единиц измерения: температура», доступного в библиотеке.

Почему я использовал список объектов вместо таблицы

Привет, я Тамаш из Tulip.

Для этого компонента я решил использовать список объектов, хранящийся в качестве значений переменных по умолчанию, поскольку набор данных редко меняется и не требует управления с помощью таблиц.

В данном случае коэффициенты и смещения при преобразовании температур представляют собой статические математические соотношения. Они не требуют редактирования во время выполнения, агрегирования или реляционных запросов. Именно поэтому я сознательно отказался от использования таблицы. Вместо этого:* я храню определения коэффициентов преобразования внутри переменной* переменная инициализируется значениями по умолчанию* список объектов обеспечивает структурированное и удобное для чтения представление данных* не требуется логика агрегирования таблиц

Такой подход позволяет сохранить легкость и автономность компонента.

Почему не таблица?

Использование таблицы повлекло бы за собой:* Необходимость добавить в приложение дополнительную таблицу* Необходимость создать запрос к этой таблице* Необходимость настроить логику агрегации для этого запроса* Необходимость добавить и поддерживать этот запрос и агрегацию внутри приложения

Для набора данных, который редко меняется, такие накладные расходы являются ненужными.

Благодаря хранению определений преобразований в качестве значений по умолчанию внутри переменной:* Компонент становится полностью переносимым* При копировании приложения копируется и структура данных* Не требуется никаких внешних зависимостей

Список объектов обеспечивает чёткое структурированное представление связанных атрибутов (inputUnit-targetUnit, factor, offset) без необходимости использования реляционной логики.

Принцип проектирования: защищённая логика, гибкая настройка

Компонент был разработан с учетом одного ключевого принципа:

Основная логика никогда не должна требовать модификации.

Для обеспечения этого я ввёл внутренние переменные компонента и обозначил их с начальным подчеркиванием (например, _temperatureInputUnit). Эти переменные с подчеркиванием выступают в роли стабильных внутренних интерфейсов, на которые может полагаться защищённая основная логика.

Компромисс: использование внутренних переменных требует двух небольших триггеров настройки:* 01 – USER CONFIG: INPUT STAGE сопоставляет внешние источники внутренним переменным* 03 – USER CONFIG: OUTPUT STAGE сопоставляет внутренний результат с желаемым местом назначения

Это добавляет тонкий конфигурационный слой вокруг защищённого движка, но при этом оставляет триггер MAGIC неизменным и удобным для копирования и вставки.

Альтернативный вариант

Более простой — но менее удобный для обслуживания — подход заключался бы в том, чтобы вставлять внешние ссылки непосредственно в выражение ядра (например, используя переменную приложения или заполнитель « table record field » прямо в коде).

Это позволяет избежать дополнительных триггеров конфигурации, но снижает переносимость и затрудняет повторное использование, поскольку каждая копия требует редактирования основной логики.

Для распределения компонентов шаблон интерфейса с использованием внутренних переменных проще в управлении: входы и выходы настраиваются на границах, в то время как вычислительный движок остаётся стабильным и защищённым.

Триггер MAGIC выполняет поиск и применяет аффинное преобразование:

result = (value × factor) + offset

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

В данной реализации:* Поддерживаются °C, K и °F* Все комбинации 3×3 предопределены* Логика динамически определяет правильный коэффициент и смещение

Разработчику приложения необходимо лишь: 1. Настроить источники входных данных; 2. Настроить место назначения выходных данных

Без переписывания формул. Без редактирования условной логики. Без структурных изменений.

Почему это масштабируемо

Такая структура позволяет:* Добавлять новые единицы измерения путём расширения списка объектов* Оставлять механизм вычислений без изменений* Сохранять архитектурную согласованность между приложениями* Быстро повторно использовать компоненты в нескольких проектах

Компонент разделяет:* Настройку источников данных (этап ввода)* Логику выполнения (MAGIC)* Маршрутизацию результатов (этап вывода)

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

Разбор триггера — как работает MAGIC (выражение)

Защищённый триггер MAGIC реализован в виде одного выражения:(@Variable._temperatureInputValue * array_value_at_index( map_to_number_list(@Variable._temperatureConversionDefaults , 'factor'), array_index_of( map_to_text_list(@Variable._temperatureConversionDefaults , 'inputUnit-targetUnit'), @Variable._temperatureInputUnit + '-' + @Variable._temperatureTargetUnit )))+array_value_at_index( map_to_number_list(@Variable._temperatureConversionDefaults , 'offset'), array_index_of( map_to_text_list(@Variable._temperatureConversionDefaults , 'inputUnit-targetUnit'), @Variable._temperatureInputUnit + '-' + @Variable._temperatureTargetUnit ))В общем плане это выражение выполняет три шага:

1. Построение ключа поиска

Ключ динамически формируется из настроенных входных переменных:

inputUnit-targetUnit

Например:

°C-°F

Это гарантирует, что логика остаётся универсальной и работает для всех поддерживаемых комбинаций.

2. Определение коэффициента и смещения из списка объектов

Выражение определяет коэффициент и смещение за один проход:* map_to_text_list(..., 'inputUnit-targetUnit') формирует список ключей поиска на лету.* array_index_of(...) находит индекс соответствующего ключа.* array_value_at_index(...) использует этот индекс для извлечения соответствующих значений из списков коэффициентов и смещений.

Ключевым дизайнерским решением здесь является непосредственное объединение array_index_of(...) с map_to_text_list(...). Это позволяет избежать хранения промежуточного списка ключей в отдельной переменной. Вместо этого список поиска генерируется непосредственно в коде и сразу же используется для извлечения как коэффициента, так и смещения в рамках одного выражения.

Благодаря этому триггер MAGIC остается самодостаточным и сокращает объем настроек, при этом по-прежнему работая как структурированный поиск — без необходимости использования таблицы или логики агрегации.

3. Применение аффинного преобразования

После определения правильного коэффициента и смещения выполняется вычисление:

result = (value × factor) + offset

Поскольку логика поиска основана на индексах и управляется данными, триггеру не требуются условные ветвления или конструкции switch-case.

Почему была выбрана именно эта структура

  • Это позволяет избежать жестко запрограммированной условной логики.
  • Она естественным образом масштабируется при добавлении новых комбинаций единиц измерения.
  • Расчет полностью зависит от данных.
  • Она поддерживает одно защищенное выражение вместо множества триггеров.

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

Расширение с помощью новых единиц измерения

Чтобы добавить новую единицу измерения, расширьте объект _temperatureConversionDefaults, добавив новую запись, содержащую:* inputUnit-targetUnit (ключ)* коэффициент* смещение

Как только новое сопоставление появляется в списке объектов, защищённый триггер MAGIC может автоматически его разрешить — изменения в основной логике не требуются.

Обработка ошибок (шаблон безопасного поиска)

Триггер MAGIC включает явное обнаружение сбоев, чтобы обеспечить безопасное выполнение в случае, если пара преобразований не найдена. Вместо того чтобы полагаться на пустой результат, триггер напрямую проверяет индекс поиска:

ARRAY_INDEX_OF( MAP_TO_TEXT_LIST(_temperatureConversionDefaults, 'inputUnit-targetUnit'), _temperatureInputUnit + '-' + _temperatureTargetUnit )

Если возвращаемый индекс равен -1, то соответствующего правила преобразования не существует.

Последовательность выполнения: 1. Очистить _temperatureConversionResult в начале триггера. 2. Если индекс поиска = -1 → не производить вычисления. 3. В противном случае → произвести вычисления и сохранить результат.

Затем на этапе OUTPUT выполняется проверка:

Если _temperatureConversionResult пусто → отобразить сообщение об ошибке.

Пример сообщения:

«Не найдено правила преобразования для выбранной пары единиц измерения. Проверьте формат единиц (°C, K, °F) и наличие этой пары в _temperatureConversionDefaults».

Это гарантирует:• Отсутствие устаревших значений от предыдущих выполнений • Отсутствие попыток вычислений без действительного правила • Получение пользователем чёткого и понятного сообщения об ошибке

Основная логика остаётся защищённой и неизменной, при этом обеспечивая детерминированное и безопасное поведение.

Почему не использовать отдельные массивы для ключей, коэффициентов и смещений?

Альтернативным подходом могло бы быть хранение:* ключей поиска в одном массиве;* коэффициентов — в другом массиве;* смещений — в третьем массиве

Хотя технически это сработало бы, это создало бы структурный риск: позиционная взаимосвязь между массивами должна всегда оставаться идеально выровненной.

Если порядок элементов в одном массиве изменится, а в остальных — нет, соотношение «ключ–коэффициент–смещение» нарушится незаметно, что приведет к некорректным преобразованиям.

Благодаря хранению связанных атрибутов вместе в списке объектов каждая запись сохраняет логическую группировку значений inputUnit-targetUnit, коэффициента и смещения. Это снижает риск нарушения согласованности и делает структуру более безопасной и простой в обслуживании.

Присоединяйтесь

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