При проектировании конфигураций на платформе 1С:Предприятие 8.3 разработчики часто сталкиваются с классической дилеммой: использовать Перечисление или Справочник для хранения аналитики. С одной стороны, набор элементов может быть небольшим и фиксированным на этапе разработки, что подталкивает к использованию перечислений. С другой стороны, бизнес-требования часто требуют расширения этого набора пользователями в режиме «1С:Предприятие», добавления дополнительных реквизитов (например, для печатных форм) и гибкого управления поведением программы.
В этой статье мы подробно рассмотрим, почему поиск элементов по коду является грубой ошибкой проектирования, разберем архитектурные паттерны решения этой задачи и выясним, как правильно организовать обращение к элементам из программного кода в соответствии со стандартами разработки фирмы «1С».
В исходном обсуждении автор темы упомянул, что использует конструкцию Справочники.ВидыУслуг.НайтиПоКоду("000001"). Давайте разберем, почему так делать категорически не рекомендуется:
"000001" ни о чем не говорят разработчику, который будет сопровождать ваш код в будущем. Приходится постоянно открывать базу данных и сверять, какая именно услуга скрывается под этим кодом.Выясним, какие альтернативные варианты проектирования существуют и как их правильно реализовать.
Если набор базовых услуг (например, «Электроснабжение», «Водоснабжение», «Отопление») известен заранее, но пользователям необходимо дать возможность добавлять свои собственные услуги и настраивать их свойства, лучшим решением будет использование предопределенных элементов справочника.
Предопределенные элементы создаются в конфигураторе. Платформа гарантирует их наличие в базе данных, связывает их по уникальному идентификатору (GUID) и позволяет напрямую обращаться к ним из программного кода по имени, заданному в конфигураторе.
Рассмотрим, как правильно обращаться к таким элементам. Прямое обращение через точку от менеджера справочника выглядит следующим образом:
СсылкаНаУслугу = Справочники.ВидыУслуг.Электроснабжение;
Однако при разработке в современных конфигурациях на базе Библиотеки стандартных подсистем (БСП) или при работе в распределенных информационных базах (РИБ) прямой вызов может привести к ошибкам, если элемент еще не создался физически в базе данных (например, при первом запуске). Поэтому стандарты разработки рекомендуют использовать безопасный метод получения предопределенных элементов:
// Получение предопределенного элемента через БСП
СсылкаНаУслугу = ОбщегоНазначения.ПредопределенныйЭлемент("Справочник.ВидыУслуг.Электроснабжение");
Если ЗначениеЗаполнено(СсылкаНаУслугу) Тогда
// Выполняем логику расчета для электроснабжения
КонецЕсли;
Использование предопределенных элементов позволяет совместить жесткую привязку логики в коде с возможностью расширения справочника пользователями.
Попытка завязать конкретный алгоритм расчета прямо на элемент справочника (даже предопределенный) — это классическая архитектурная ошибка. Представим ситуацию: у вас есть услуга «Электроснабжение», расчет которой идет по показаниям приборов учета. Завтра пользователям потребуется завести услугу «Электроснабжение (день)» и «Электроснабжение (ночь)». Если ваш код жестко завязан на элемент «Электроснабжение», вам придется переписывать конфигурацию.
Чтобы избежать этого, применим паттерн «Стратегия»:
ПоПоказаниямПриборов, ПоПлощадиПомещения, ФиксированнойСуммой. Это перечисление определяет правила игры и алгоритмы, оно меняется только разработчиком.СпособРасчета с типом ссылки на наше перечисление.Проанализируем пример реализации такого подхода в модуле расчета:
Процедура РассчитатьСтоимостьУслуги(СтрокаДокумента)
ТекущаяУслуга = СтрокаДокумента.Услуга;
// Проверяем способ расчета, а не конкретную ссылку на справочник
Если ТекущаяУслуга.СпособРасчета = Перечисления.СпособыРасчетаУслуг.ПоПоказаниямПриборов Тогда
ВыполнитьРасчетПоПриборамУчета(СтрокаДокумента);
ИначеЕсли ТекущаяУслуга.СпособРасчета = Перечисления.СпособыРасчетаУслуг.ПоПлощадиПомещения Тогда
ВыполнитьРасчетПоПлощади(СтрокаДокумента);
КонецЕсли;
КонецПроцедуры
При таком подходе пользователи могут плодить десятки видов услуг с разными наименованиями и свойствами для отчетов. Главное — они укажут правильный способ расчета, и система отработает корректно без вмешательства программиста.
Если вам категорически нельзя менять конфигурацию (добавлять предопределенные элементы или реквизиты), либо если в разных организациях одной базы правила соответствия услуг должны быть разными, рассмотрим вариант с использованием Регистра сведений.
Создадим регистр сведений СоответствиеВидовУслуг со следующей структурой:
ИдентификаторУслуги (Тип: Перечисление или Строка, определяющая системную роль, например, "Электричество").ВидУслуги (Тип: СправочникСсылка.ВидыУслуг).В режиме «1С:Предприятие» пользователь сам один раз сопоставляет системную роль услуги с конкретным элементом своего справочника. В коде мы обращаемся к регистру, чтобы получить нужную ссылку:
Функция ПолучитьСсылкуНаУслугуПоРоли(СистемнаяРоль) Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| СоответствиеВидовУслуг.ВидУслуги КАК ВидУслуги
|ИЗ
| РегистрСведений.СоответствиеВидовУслуг КАК СоответствиеВидовУслуг
|ГДЕ
| СоответствиеВидовУслуг.ИдентификаторУслуги = &СистемнаяРоль";
Запрос.УстановитьПараметр("СистемнаяРоль", СистемнаяРоль);
УстановитьПривилегированныйРежим(Истина);
РезультатЗапроса = Запрос.Выполнить();
УстановитьПривилегированныйРежим(Ложь);
Если Не РезультатЗапроса.Пустой() Тогда
Выборка = РезультатЗапроса.Выбрать();
Выборка.Следующий();
Возврат Выборка.ВидУслуги;
КонецЕсли;
Возврат Справочники.ВидыУслуг.ПустаяСсылка();
КонецФункции
Этот способ является максимально гибким. Он полностью исключает жесткое кодирование конкретных элементов справочника и переносит ответственность за сопоставление на администратора системы.
Автор темы упомянул необходимость «подвесить дополнительные поля» к услугам, например, написание в различных отчетах или макеты печати. Разберем, как сделать это красиво:
Если Услуга = Электричество Тогда....Подведем итог нашего анализа. Выбор архитектурного решения зависит от сложности системы: