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, коэффициента и смещения. Это снижает риск нарушения согласованности и делает структуру более безопасной и простой в обслуживании.
Присоединяйтесь
Присоединяйтесь к сообществу, чтобы делиться идеями по улучшению, предлагать новые единицы измерения, сообщать о проблемах или обсуждать архитектурные решения.
