Часто в приложениях необходимо работать с единой сущностью, экземпляры которой могут иметь различные свойства. Практический пример - это товар в магазине. Несмотря на то, что все товары обрабатываются одинаково, каждый из них может иметь собственные характеристики. Конечно, можно обойтись только текстовым описанием, но тогда теряется возможность поиска и фильтрации по заданным свойствам.
В предыдущей статье о том, как создать товар, состоящий из других товаров, в качестве примера был сделан онлайн-магазин на декларативном фреймворке Evado. В этой статье возьмем за основу это же приложение и улучшим его до второй версии.
Выбор решения
Итак, есть два способа создать дополнительные свойства. Каждый со своими плюсами и минусами.
Первый способ - это выделить свойство товара в отдельную сущность. Тогда при создании объекта товара, будут создаваться все необходимые объекты его свойств. Плюс такого подхода - это возможность задавать уникальный набор характеристик для каждого экземпляра товара. Минус - это затраты на создание отдельных объектов каждого свойства.
Второй способ - это создание отдельных типов (классов) товаров для которых описываются дополнительные свойства. Каждый тип содержит как данные основного товара, так и новые. Плюс такого решения - это удобная работа с новыми свойствами, которые появляются на форме товара как дополнительные атрибуты. Минус - это необходимость заранее определить нужные типы товаров.
Во фреймворке Evado второй способ решается через наследование класса. В наследнике можно добавить или переопределить данные родителя.
Для приложения магазина потребуется добавить атрибуты, описывающие дополнительные свойства товара. Кроме того, необходимо будет обозначить их принадлежность к таким свойствам.
Реализация
Устанавливаем веб-приложение «Книжный магазин». Его можно запустить либо через Docker, либо в окружении сервера Node.js и базы данных MongoDB. Запускаем и переходим в модуль Студия для редактирования метаданных.
Обратите внимание, что в репозитории активна последняя версия приложения с уже сделанными изменениями, описанными в данной статье. Исходная версия доступна по метке v1.0.0.
У нас уже есть основной класс Товар. Создаем в нем группу атрибутов Дополнительные свойства. Она необходима для обозначения атрибутов, которые будут описывать дополнительные свойства. В классе Товар эта группа остается пустой.
Создаем новый класс Карандаш, унаследовав класс Товар. Все данные из родительского класса доступны в наследнике. Добавляем атрибуты Цвет и Грифель и указываем им принадлежность к группе Дополнительные свойства. Задаем для атрибутов соответствующие перечислимые значения.
Перечислимые значения определяют список допустимых значений атрибута. На форме они могут быть представлены либо выпадающим списком, либо списком переключателей.
Экспортируем метаданные в metadata/app и переходим в модуль Офис для эксплуатации приложения.
Переходим в пункт меню Все товары. Здесь отображаются объекты класса Товар, а также объекты его классов-потомков. Нажимаем кнопку Создать. Нам будет предложено выбрать целевой класс. Если выбрать класс Карандаш, то на форме создания появятся атрибуты, описывающие его дополнительные свойства.
Обратите внимание, что при отображении списка всех товаров могут быть показаны только атрибуты базового класса Товар, потому что они есть у любого экземпляра класса или его наследников. Соответственно, и общий поиск работает только по таким атрибутам.
Однако найти товар из общего списка по атрибутам наследников можно. Для этого используем фильтр и выбираем Атрибуты классов-потомков. Указываем нужный класс, его атрибут и условие. Поиск будет произведен среди всех экземпляров текущего класса и его наследников.
Представления классов
Отдельно хочу сказать про представления классов-потомков. По умолчанию все представления базового класса копируются в наследника как ссылки. Тем самым изменения оригинального представления автоматически будет распространяться на наследников.
Однако, в некоторых случаях, требуется изменить представление в наследнике. Так, для публичного представления товара, необходимо добавить атрибуты дополнительных свойств.
Представления переопределяют параметры и функционал классов для использования в некоторых случаях. Например, при отображении объектов в списке требуется показать только часть атрибутов. Кроме того, система безопасности может разрешать доступ к объектам лишь в определенном представлении.
Переходим в класс Карандаш и удаляем связанное представление Публичный вид. Затем клонируем его же из родительского класса. Теперь представление можно редактировать. Добавляем в него новые атрибуты класса Карандаш.
Пользовательский интерфейс
Пользовательский интерфейс может быть реализован любыми средствами и даже находиться на другом сервере. В данном случае он реализован как модуль Front. На стороне клиента используется Vue.js. Все данные получаются через AJAX-запросы к API приложения.
Для отображения товара с дополнительными свойствами необходимо запрашивать, помимо самого объекта, метаданные представления, которые зависят от класса объекта. Из этих метаданных получаем список атрибутов. Фильтруем их по принадлежности к группе Дополнительные свойства и отображаем соответствующие данные экземпляра товара.
Заключение
Наследование удобный способ расширять функционал, хотя и менее гибкий, чем компонентный подход. Если типы данных известны заранее, то наследование будет работать «прозрачно» для пользователя, подстраиваясь под конкретный экземпляр сущности.
Готовое веб-приложение магазина находится в свободном доступе.