Сразу хочется отметить, что речь пойдет о повторном использовании методов, которые фактически являются «статическими» в терминологии ООП, то есть не связанных с конкретным экземпляром объекта. Такие методы, не изменяя состояния (данных) самого объекта, выполняют некоторые действия или (и) возвращают результаты каких-то вычислений.
Как известно, платформа 1С-8.2/8.3 предоставляет возможность повторного использования кода путем размещения его в экспортных процедурах и функциях в следующих местах конфигурации:
- Модули объектов "Форма" и "УправляемаяФорма";
- Общие модули конфигурации;
- Модули менеджеров объектов;
- Модули объектов;
Рассмотрим подробней каждый из вариантов с точки зрения использования в управляемых формах в обработках или отчетах.
Модули объектов форм (по замечанию awk):
Этот вариант переиспользования не очень удобен по ряду причин:
- Объекты "Форма" или "УправляемаяФорма" отдельно создать можно только на стороне клиентов (метод ПолучитьФорму() не доступен на сервере). Поэтому на стороне сервера применение этого варианта переиспользования кода ограничено серверным контекстом управляемых форм.
- В силу узкой специализации объектов "Форма" или "УправляемаяФорма", призванных решать интерфейсные задачи.
На практике этот вариант переиспользования кода в основном используется для нестандартного начального заполнения данных формы до ее открытия или как один из возможных вариантов организации взаимодействия между между формами (в подчиненной форме через атрибут "ВладелецФормы" можно вызвать экспортный метод формы-владельца).
Общие модули конфигурации:
Этот вариант является самым простым с точки зрения использования на стороне клиента в управляемых формах, поскольку общие модули и их методы можно сделать непосредственно доступными для использования на клиенте:
&НаКлиенте
Процедура ОбработчикСобытия(Элемент…)
//…
ВычЗначение = ИмяОбщегоМодуля1.МетодФункция1(Пар1,Пар2,Пар3…);
//…
ИмяОбщегоМодуля2.МетодПроцедура2(Арг1,Арг2,Арг3…);
//…
КонецПроцедуры
Недостаток размещения кода в общих модулях заключается в уменьшении универсальности обработки (отчета).
Ее функционал «размазывается» по конфигурации. При переносе функционала в другое прикладное решение приходится помнить, что кроме самой обработки также требуется подтянуть «то, пятое, десятое».
А для универсальных обработок, используемых в качестве внешних, такая зависимость от общих модулей может оказаться вообще неприемлемой.
Модули менеджеров объектов:
Модуль менеджера объекта является естественным местом размещения кода общего назначения, не связанного с конкретным экземпляром объекта.
К сожалению, в нынешней реализации управляемого приложения модуль менеджера объекта не доступен на стороне клиента.
Поэтому вызов методов этого модуля приходится выполнять через вспомогательные серверные внеконтекстные методы формы:
&НаСервереБезКонтекста
Функция ФормМетодФункция1(Пар1,Пар2,Пар3…)
Возврат Обработки[ИмяОбработки]. МетодФункция1 (Пар1,Пар2,Пар3…);
КонецФункции
&НаСервереБезКонтекста
Процедура ФормМетодПроцедура2(Арг1,Арг2,Арг3…)
Обработки[ИмяОбработки]. МетодПроцедура2(Арг1,Арг2,Арг3…);
КонецПроцедуры
//…
&НаКлиенте
Процедура ОбработчикСобытия(Элемент…)
//…
ВычЗначение = ФормМетодФункция1(Пар1,Пар2,Пар3…);
//…
ФормМетодПроцедура2(Арг1,Арг2,Арг3…);
//…
КонецПроцедуры
В принципе такой вариант размещения кода является приемлемым для обработки с точки зрения производительности и переносимости. Но здесь возникает затруднение, если предполагается использовать обработку (отчет) в качестве внешней обработки (внешнего отчета).
Дело в том, что у внешних обработок и отчетов нет менеджера объекта как такового.
А при сохранении встроенной обработки во внешнюю обработку модуль ее менеджера попросту теряется (кстати, без предупреждения).
В этом случае остается единственное место для размещения повторно используемого кода – модуль объекта обработки (отчета).
Модули объектов:
Модуль объекта также не доступен на стороне клиента в управляемой форме.
Вызов методов объекта в общем случае возможен через вспомогательные серверные, но уже контекстные методы формы:
&НаСервере
Функция ФормМетодФункция1(Пар1,Пар2,Пар3…)
Обработка = РеквизитФормыВЗначение("Объект");
Возврат Обработка. МетодФункция1 (Пар1,Пар2,Пар3…);
КонецФункции
&НаСервере
Процедура ФормМетодПроцедура2(Арг1,Арг2,Арг3…)
Обработка = РеквизитФормыВЗначение("Объект");
Обработка. МетодПроцедура2(Арг1,Арг2,Арг3…);
КонецПроцедуры
//…
&НаКлиенте
Процедура ОбработчикСобытия(Элемент…)
//…
ВычЗначение = ФормМетодФункция1(Пар1,Пар2,Пар3…);
//…
ФормМетодПроцедура2(Арг1,Арг2,Арг3…);
//…
КонецПроцедуры
Серверные контекстные вызовы являются «затратными». Их использование оправдано, если в массовом порядке изменяются данные формы или когда для выполнения каких-то действий требуются данные всей формы или связанного с ней объекта.
Поэтому рекомендуется по возможности избегать использования серверных контекстных вызовов.
Что же делать, если метод объекта фактически является «статическим» и не использует данные самого объекта?
Например, пусть метод возвращает список значений меню, динамически зависящего только от значений переданных методу параметров. Ясно, что использование контекстного серверного вызова в этой ситуации явно неоправданно.
В работе //infostart.ru/public/236344/ при разработке управляемых форм обработки для решения этой проблемы был использован такой подход:
В специально предназначенном строковом реквизите обработки при инициализации модуля объекта сохраняется внутреннее строковое представление типа объекта обработки/отчета:
// установим внутреннее строковое представление типа значения объекта обработки
ЭтотОбъект.ОбработкаТип = ЗначениеВСтрокуВнутр(ТипЗнч(ЭтотОбъект));
Этот способ хорош тем, что "сразу" инициалициализирует нужный реквизит объекта обработки/отчета во всех формах, где объект обработки/отчета используется.
В принципе реквизит обработки можно заменить реквизитом самой формы.
Тогда описанное выше действие нужно будет выполнять в обработчике «ПриСозданииНаСервере»:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
Обработка = РеквизитФормыВЗначение("Объект");
// установим внутреннее строковое представление типа значения объекта обработки
ЭтаФорма.ОбработкаТип = ЗначениеВСтрокуВнутр(ТипЗнч(Обработка));
//…
ЗначениеВРеквизитФормы(Обработка, "Объект");
КонецПроцедуры
Значение этого реквизита в дальнейшем используется во вспомогательных внеконтекстных серверных методах формы для создания объекта обработки. Ну, а сам вызов методов объекта обработки на стороне клиента выглядит следующим образом:
&НаСервереБезКонтекста
Функция ФормМетодФункция1(ОбработкаТип,Пар1,Пар2,Пар3)
// создаем объект обработки по внутреннему строковому представлению его типа
// для внеконтекстного выполнения экспортных методов
Обработка = Новый (ЗначениеИзСтрокиВнутр(ОбработкаТип));
Возврат Обработка. МетодФункция1 (Пар1,Пар2,Пар3…);
КонецФункции
&НаСервереБезКонтекста
Процедура ФормМетодПроцедура2(ОбработкаТип,Арг1,Арг2,Арг3…)
// создаем объект обработки по внутреннему строковому представлению его типа
// для внеконтекстного выполнения экспортных методов
Обработка = Новый (ЗначениеИзСтрокиВнутр(ОбработкаТип));
Обработка. МетодПроцедура2(Арг1,Арг2,Арг3…);
КонецПроцедуры
В
//…
&НаКлиенте
Процедура ОбработчикСобытия(Элемент…)
//…
ВычЗначение = ФормМетодФункция1(Объект.ОбработкаТип,Пар1,Пар2,Пар3…);
//…
ФормМетодПроцедура2(Объект.ОбработкаТип,Арг1,Арг2,Арг3…);
//…
КонецПроцедуры
В
Таким образом, решается поставленная задача:
вызовы методов объекта осуществляется при внеконтекстных серверных вызовах со стороны клиента в управляемой форме.
Кроме этого описанный выше подход более универсален, чем использование модуля менеджера объекта.
Так как при таком создании объекта в явном виде не используется информация о том, что за объект создается (объект обработки, отчета или чего другого), а также информация об имени метаданных объекта.
Применение:
Описанный в данной публикации подход может оказаться полезным, если у разрабатывемого объекта планируется создание как управляемого, так и обычного интерфейса.
Ограничения применения:
В случае внешних отчетов и обработок предлагаемый подход можно использовать только в том соединении с информационной базой, в котором был открыт файл обработки/отчета (был откомпилирован модуль объекта из файла).
При использовании в другом соединении (например, в фоновом задании или во внешнем соединении) у нас не получится создать объект внешней обработки/отчета по переданному внутреннему строковому представлению типа объекта обработки/отчета (смотрите первый пример ниже по тексту).
Поскольку в этом случае функция ЗначениеИзСтрокиВнут() вернет значение Тип(“Неопределено”) вместо типа нужного нам объекта.
Например, может возникнуть вполне естественное желание запустить на выполнение обработку в фоновом задании.
Для этого потребуется экспортный метод некоего общего модуля, позволяющего выполнять произвольный код на стороне сервера.
Пусть это будет общий модуль «УТР_Сервер» и метод «ВыполнитьНаСервере» (реальный пример из конфигурации «Управление аптечной сетью»):
Процедура ВыполнитьНаСервере(Строка, ПереданноеЗначение=Неопределено) Экспорт
Выполнить(Строка);
КонецПроцедуры
Запустить обработку на выполнение в фоновом задании с передачей типа объекта обработки для его создания можно попытаться так:
&НаСервереБезКонтекста
Функция ЗапуститьНаВыполнениеВФоновомЗаданииНаСервере1(ОбработкаТип,стАргументы,ТаймаутФЗ,ФоновоеЗаданиеКлюч,ЕррорИнфо)
Обработка = Новый(ЗначениеИзСтрокиВнутр(ОбработкаТип));
ОбработкаИмя = Обработка.Метаданные().Имя;
стПараметры = Новый Структура("А",стАргументы);
стПараметры.Вставить("ОбработкаТип",ОбработкаТип);
КодВыполнения =
"П = ПереданноеЗначение;
|// по переданному типу объекта обработки - НЕ РАБОТАЕТ !!!
|Обработка = Новый(ЗначениеИзСтрокиВнутр(П.ОбработкаТип));
|Обработка.МетодПроцедура2(П.А.Арг1,П.А.Арг2,П.А.Арг3);
|";
ИмяМетодаФЗ = "УТР_Сервер.ВыполнитьНаСервере";
ПараметрыФЗ = Новый Массив;
ПараметрыФЗ.Добавить(КодВыполнения); // исполняемый код, запускающий обработку на сервере
ПараметрыФЗ.Добавить(стПараметры); // параметры исполняемого кода
ЕррорИнфо = "";
Попытка
Задание = ФоновыеЗадания.Выполнить(
ИмяМетодаФЗ,ПараметрыФЗ,ОбработкаИмя+"_"+ФоновоеЗаданиеКлюч,"Выполнение обработки """+ОбработкаИмя+"""");
Если ТаймаутФЗ > 0 Тогда
Задание.ОжидатьЗавершения(ТаймаутФЗ);
КонецЕсли;
ЗаданиеGUID = Задание.УникальныйИдентификатор;;
Исключение
ЗаданиеGUID = Неопределено;
ЕррорИнфо = ОписаниеОшибки();
КонецПопытки;
Возврат ЗаданиеGUID;
КонецФункции
Обход ограничений:
Что же делать, если очень хочется выполнить метод объекта внешней обработки/отчета именно в фоновом задании?
Тогда придется как-то передать фоновому заданию двоичные данные обработки (отчета) для создания ее (его) объекта.
Для этого есть три основных способа:
1) Передать через аргумент метода фонового задания навигационную ссылку на данные обработки/отчета в информационной базе
(для этого обработка должна быть сохранена в информационное базе,
например, в справочнике «ДополнительныеОтчетыИОбработки»):
&НаСервереБезКонтекста
Функция ПолучитьНавигационнуюСсылкуДопОбработки(ИмяОбработки)
УстановитьПривилегированныйРежим(Истина);
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Т.Ссылка КАК Ссылка
|ИЗ
| Справочник.ДополнительныеОтчетыИОбработки КАК Т
|ГДЕ
| Т.ПометкаУдаления = ЛОЖЬ
| И Т.ИмяОбъекта = """+ИмяОбработки+"""
| И Т.Вид = ЗНАЧЕНИЕ(Перечисление.ВидыДополнительныхОтчетовИОбработок.ДополнительнаяОбработка)
| И Т.Публикация = ЗНАЧЕНИЕ(Перечисление.ВариантыПубликацииДополнительныхОтчетовИОбработок.Используется)";
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Количество() = 0 Цикл
Возврат "";
КонецЦикла;
Выборка.Следующий();
Возврат ПолучитьНавигационнуюСсылку(Выборка.Ссылка,"ХранилищеОбработки");
КонецФункции
&НаСервереБезКонтекста
Функция ЗапуститьНаВыполнениеВФоновомЗаданииНаСервере2(ОбработкаТип,стАргументы,ТаймаутФЗ,ФоновоеЗаданиеКлюч,ЕррорИнфо)
Обработка = Новый(ЗначениеИзСтрокиВнутр(ОбработкаТип));
ОбработкаИмя = Обработка.Метаданные().Имя;
стПараметры = Новый Структура("А",стАргументы);
стПараметры.Вставить("ОбработкаАдрес",ПолучитьНавигационнуюСсылкуДопОбработки(ОбработкаИмя));
КодВыполнения =
"П = ПереданноеЗначение;
|// по навигационной ссылке двоичных данных обработки в ИБ
|Обработка = ВнешниеОбработки.Создать(ВнешниеОбработки.Подключить(П.ОбработкаАдрес,,Ложь));
|Обработка.МетодПроцедура2(П.А.Арг1,П.А.Арг2,П.А.Арг3);
|";
ИмяМетодаФЗ = "УТР_Сервер.ВыполнитьНаСервере";
ПараметрыФЗ = Новый Массив;
ПараметрыФЗ.Добавить(КодВыполнения); // исполняемый код, запускающий обработку на сервере
ПараметрыФЗ.Добавить(стПараметры); // параметры исполняемого кода
ЕррорИнфо = "";
Попытка
Задание = ФоновыеЗадания.Выполнить(
ИмяМетодаФЗ,ПараметрыФЗ,ОбработкаИмя+"_"+ФоновоеЗаданиеКлюч,"Выполнение обработки """+ОбработкаИмя+"""");
Если ТаймаутФЗ > 0 Тогда
Задание.ОжидатьЗавершения(ТаймаутФЗ);
КонецЕсли;
ЗаданиеGUID = Задание.УникальныйИдентификатор;;
Исключение
ЗаданиеGUID = Неопределено;
ЕррорИнфо = ОписаниеОшибки();
КонецПопытки;
Возврат ЗаданиеGUID;
КонецФункции
2) Передать через аргумент метода фонового задания полный путь к файлу обработки/отчета
(полный путь можно получить из атрибута объекта “ИспользуемоеИмяФайла”,
этот путь должен быть доступен на стороне сервера):
&НаСервереБезКонтекста
Функция ЗапуститьНаВыполнениеВФоновомЗаданииНаСервере3(ОбработкаТип,стАргументы,ТаймаутФЗ,ФоновоеЗаданиеКлюч,ЕррорИнфо)
Обработка = Новый(ЗначениеИзСтрокиВнутр(ОбработкаТип));
ОбработкаИмя = Обработка.Метаданные().Имя;
стПараметры = Новый Структура("А",стАргументы);
стПараметры.Вставить("ОбработкаПутьФайла",Обработка.ИспользуемоеИмяФайла);
КодВыполнения =
"П = ПереданноеЗначение;
|// по пути к файлу обработки
|Обработка = ВнешниеОбработки.Создать(П.ОбработкаПутьФайла,Ложь);
|Обработка.МетодПроцедура2(П.А.Арг1,П.А.Арг2,П.А.Арг3);
|";
ИмяМетодаФЗ = "УТР_Сервер.ВыполнитьНаСервере";
ПараметрыФЗ = Новый Массив;
ПараметрыФЗ.Добавить(КодВыполнения); // исполняемый код, запускающий обработку на сервере
ПараметрыФЗ.Добавить(стПараметры); // параметры исполняемого кода
ЕррорИнфо = "";
Попытка
Задание = ФоновыеЗадания.Выполнить(
ИмяМетодаФЗ,ПараметрыФЗ,ОбработкаИмя+"_"+ФоновоеЗаданиеКлюч,"Выполнение обработки """+ОбработкаИмя+"""");
Если ТаймаутФЗ > 0 Тогда
Задание.ОжидатьЗавершения(ТаймаутФЗ);
КонецЕсли;
ЗаданиеGUID = Задание.УникальныйИдентификатор;;
Исключение
ЗаданиеGUID = Неопределено;
ЕррорИнфо = ОписаниеОшибки();
КонецПопытки;
Возврат ЗаданиеGUID;
КонецФункции
В
3) Передать через аргумент метода фонового задания сами двоичные данные файла обработки/отчета
(их можно получить конструктором по значению атрибута объекта “ИспользуемоеИмяФайла”):
&НаСервереБезКонтекста
Функция ЗапуститьНаВыполнениеВФоновомЗаданииНаСервере4(ОбработкаТип,стАргументы,ТаймаутФЗ,ФоновоеЗаданиеКлюч,ЕррорИнфо)
Обработка = Новый(ЗначениеИзСтрокиВнутр(ОбработкаТип));
ОбработкаИмя = Обработка.Метаданные().Имя;
стПараметры = Новый Структура("А",стАргументы);
стПараметры.Вставить("ОбработкаДанные",Новый ДвоичныеДанные(Обработка.ИспользуемоеИмяФайла));
КодВыполнения =
"П = ПереданноеЗначение;
|// по двоичным данным обработки
|ИмяФайла = ПолучитьИмяВременногоФайла("".epf"");
|П.ОбработкаДанные.Записать(ИмяФайла);
|Обработка = ВнешниеОбработки.Создать(ИмяФайла,Ложь);
|Обработка.МетодПроцедура2(П.А.Арг1,П.А.Арг2,П.А.Арг3);
|";
ИмяМетодаФЗ = "УТР_Сервер.ВыполнитьНаСервере";
ПараметрыФЗ = Новый Массив;
ПараметрыФЗ.Добавить(КодВыполнения); // исполняемый код, запускающий обработку на сервере
ПараметрыФЗ.Добавить(стПараметры); // параметры исполняемого кода
ЕррорИнфо = "";
Попытка
Задание = ФоновыеЗадания.Выполнить(
ИмяМетодаФЗ,ПараметрыФЗ,ОбработкаИмя+"_"+ФоновоеЗаданиеКлюч,"Выполнение обработки """+ОбработкаИмя+"""");
Если ТаймаутФЗ > 0 Тогда
Задание.ОжидатьЗавершения(ТаймаутФЗ);
КонецЕсли;
ЗаданиеGUID = Задание.УникальныйИдентификатор;;
Исключение
ЗаданиеGUID = Неопределено;
ЕррорИнфо = ОписаниеОшибки();
КонецПопытки;
Возврат ЗаданиеGUID;
КонецФункции
В
Описание файлов поставки:
Пример.epf – демонстрационная обработка с примерами использования
(в том числе разные варианты выполнения в фоновом задании):