Переходя с платформы «1С:Предприятие 8» на «1С:Предприятие 7.7» (или поддерживая старые конфигурации), разработчики часто сталкиваются с архитектурным тупиком. В «восьмерке» существует четкое разделение: вся бизнес-логика, не связанная напрямую с интерфейсом (расчеты, заполнение реквизитов, проверки), размещается в модуле объекта. Этот модуль доступен всегда, когда мы создаем объект программно. В «семерке» же структура модулей принципиально иная, и попытка найти «модуль объекта» для аналогичных задач вызывает массу вопросов.
В этой статье мы подробно разберем, почему в 1С 7.7 нет штатного модуля объекта в привычном понимании, как правильно работать с контекстом и какие существуют пути решения этой проблемы — от классических «костылей» до элегантного объектно-ориентированного программирования с использованием внешней компоненты 1С++.
---
Для начала проанализируем, какими инструментами мы располагаем в штатной поставке 1С 7.7. У нас есть три основных места для размещения программного кода:
Экспорт, мы можем вызвать ее откуда угодно.СоздатьОбъект("Документ.Имя"), нельзя.Таким образом, если нам нужно написать функцию, которая рассчитывает скидку по документу или проверяет заполнение его реквизитов, и мы хотим вызывать ее как при открытой форме документа, так и при программном переборе документов в обработке, у нас есть два пути: штатный (через Глобальный модуль) и альтернативный (через классы 1С++). Рассмотрим оба варианта подробно.
---
Чтобы не дублировать код во всех формах и обработках, где требуется обработка данных документа, мы вынуждены выносить бизнес-логику в Глобальный модуль. Но как функция в Глобальном модуле узнает, с каким именно документом мы сейчас работаем, и как она сможет изменить данные на форме?
Для этого используется передача контекста. Разберем этот механизм по шагам.
глВзятьКонтекст()В типовых конфигурациях 1С 7.7 (например, в «Торговле и Складе» или «Комплексной») вы наверняка встречали такую странную функцию в Глобальном модуле:
Функция глВзятьКонтекст(Конт) Экспорт
Возврат Конт;
КонецФункции
Зачем нужен этот «посредник», который просто возвращает то, что получил на вход? Выясним причину.
Дело в том, что ключевое слово Контекст в 1С 7.7 является системной зарезервированной псевдопеременной, а не полноценным объектом. Если мы попытаемся передать слово Контекст напрямую в некоторые штатные функции (например, в параметры метода ОткрытьФорму() или внутрь строкового вычисления через Шаблон()), платформа часто выдает ошибку компиляции либо передает пустое значение. Пропуская Контекст через простейшую экспортную функцию глВзятьКонтекст(Контекст), мы заставляем ядро 1С вычислить эту псевдопеременную и вернуть уже полноценную ссылку на групповой контекст формы. Эту ссылку уже можно безопасно передавать в любые процедуры.
Допустим, нам нужно написать функцию расчета скидки. В Глобальном модуле мы определяем следующую функцию:
Функция глРассчитатьСкидкуДокумента(Конт) Экспорт
// Через переданный контекст мы имеем полный доступ к реквизитам формы
Если Конт.ВидОперации = Перечисление.ВидыОпераций.Опт Тогда
Конт.Скидка = Конт.Итог("Сумма") * 0.05;
Иначе
Конт.Скидка = 0;
КонецЕсли;
// Мы также можем вызывать процедуры самой формы, если они объявлены как Экспорт
Попытка
Конт.ПересчитатьВсеИтоги();
Исключение
// На случай, если в форме нет такой процедуры
КонецПопытки;
КонецФункции
Теперь из модуля формы документа мы можем легко вызвать эту функцию, передав собственный контекст:
Процедура ПриЗаписи()
глРассчитатьСкидкуДокумента(глВзятьКонтекст(Контекст));
КонецПроцедуры
Минусы штатного подхода: Глобальный модуль быстро раздувается до неимоверных размеров, превращаясь в «свалку» разнородных функций. Кроме того, для внесения любых изменений в Глобальный модуль требуется монопольный доступ к базе данных, что заставляет выгонять всех пользователей из системы.
---
Если в вашем проекте уже используется или планируется к использованию легендарная внешняя компонента 1С++, мы можем решить задачу создания полноценного модуля объекта на уровне современных стандартов программирования.
1С++ позволяет создавать полноценные пользовательские классы. Мы можем создать класс, который будет выступать в роли «модуля объекта» для нашего документа.
В каталоге вашей базы данных (ИБ) находим или создаем текстовый файл defcls.prm. В нем мы прописываем объявление нашего нового класса:
класс АнализДокумента = classes\АнализДокумента.ert {}
Здесь мы указали системе, что при создании объекта с типом «АнализДокумента» логика будет считываться из внешней обработки classes\АнализДокумента.ert.
Создаем обычную внешнюю обработку в конфигураторе, называем ее АнализДокумента.ert и сохраняем в подкаталог classes вашей базы данных. В модуле этой обработки мы пишем наши методы с ключевым словом Экспорт:
// В модуле обработки classes\АнализДокумента.ert
Функция РассчитатьСкидку(Конт) Экспорт
// Конт — это переданный контекст документа или формы
Если Конт.ФлагОпcommit = 1 Тогда
Возврат Конт.Сумма * 0.1;
Иначе
Возврат 0;
КонецЕсли;
КонецФункции
Теперь мы можем из любого места (из другой обработки, отчета или формы) создать экземпляр нашего класса и выполнить логику:
// Создаем объект класса 1С++
обр = СоздатьОбъект("АнализДокумента");
// Вызываем метод класса, передавая контекст напрямую
// Обратите внимание: при использовании 1С++ глВзятьКонтекст() не обязателен!
Результат = обр.РассчитатьСкидку(Контекст);
Предупреждение("Рассчитанная скидка: " + Результат);
Благодаря внутренней архитектуре 1С++, компонента сама корректно обрабатывает системную переменную Контекст и передает ее внутрь класса без искажений.
---
Иногда при попытке вызвать метод формы через переданный контекст в стороннем модуле разработчики получают ошибку: «Поле агрегатного объекта не обнаружено». В штатной 1С 7.7 действительно нельзя обратиться к экспортному методу формы «через точку» от переданного контекста.
Эту проблему решает технология TurboBL, которая встроена в ядро 1С++. Она перехватывает обращения к контекстам и динамически перенаправляет вызовы методов, а также значительно ускоряет работу за счет кэширования и хэширования имен функций (вместо медленного штатного линейного поиска по тексту модуля).
Чтобы гарантировать работу этого механизма, обязательно инициализируйте TurboBL при старте системы (например, в процедуре ПриНачалеРаботыСистемы Глобального модуля):
Процедура ПриНачалеРаботыСистемы()
// Инициализация настроек 1С++
Попытка
оНастр = СоздатьОбъект("УправлениеНастройками");
оНастр.Включить("TurboBL");
Исключение
Сообщить("Не удалось запустить оптимизацию TurboBL!");
КонецПопытки;
КонецПроцедуры
---
Если вы разрабатываете тиражное решение или администрируете сеть из сотен и тысяч распределенных баз данных, разворачивать и контролировать обновление множества внешних файлов .ert в папках classes на местах становится кошмаром для системного администратора. В версиях файлов легко запутаться.
Для таких случаев 1С++ предлагает элегантное решение — хранить код классов прямо внутри структуры метаданных (в MD-файле), оформляя их в виде обычных внутренних обработок конфигурации.
В этом случае объявление класса в файле defcls.prm будет выглядеть следующим образом:
класс ТДокумент = класс_ТДокумент@MD : Документ
{
};
Разберем, что означает эта запись:
ТДокумент — имя нашего нового класса.класс_ТДокумент@MD — указывает 1С++, что код нужно брать из внутренней обработки конфигуратора с идентификатором класс_ТДокумент.: Документ — это механизм наследования. Наш класс автоматически наследует все свойства и методы системного объекта «Документ».Благодаря этому наследованию, внутри модуля нашей обработки класс_ТДокумент мы можем напрямую использовать системные методы документов:
// Код внутренней обработки класс_ТДокумент
Функция ПолучитьСуммуСНДС() Экспорт
// Нам доступны методы базового класса "Документ" без явной передачи контекста!
Возврат ТекущийДокумент().Сумма * 1.2;
КонецФункции
В коде вызова это выглядит поразительно просто и очень напоминает ООП в 1С 8.х:
ДокОбъект = СоздатьОбъект("ТДокумент");
Если ДокОбъект.НайтиДокумент(ВыбраннаяСсылка) = 1 Тогда
СуммаСНДС = ДокОбъект.ПолучитьСуммуСНДС();
КонецЕсли;
---
Одним из главных преимуществ хранения классов во внешних файлах (Вариант 2) является возможность их динамического обновления. В штатной 1С 7.7 для изменения Глобального модуля нужно выгнать всех пользователей, чтобы монопольно сохранить MD. С классами 1С++ этого делать не нужно.
По умолчанию 1С++ оптимизирует работу и кэширует код классов в памяти. Чтобы изменения во внешних файлах .ert применились у пользователей мгновенно без перезапуска программы, мы можем программно сбросить кэш оптимизации.
Создадим простую обработку для администратора, которая будет выполнять следующий код:
// Сброс кэша классов для применения обновлений "на лету"
СервисМетаданных = СоздатьОбъект("MetaInfoClasses");
СервисМетаданных.ОчиститьДанныеОптимизацииКлассов();
Предупреждение("Код классов успешно обновлен у всех пользователей!");
Также на время активной разработки и отладки вы можете временно отключить оптимизацию кэширования классов в настройках, установив параметр EnableOptimization в значение 0, чтобы файлы классов перечитывались с диска при каждом обращении.
Мы подробно разобрали эволюцию работы с бизнес-логикой документов в 1С 7.7. Для простых и редких задач вполне подойдет классический метод с передачей контекста в Глобальный модуль через глВзятьКонтекст. Для крупных, развивающихся и масштабируемых систем оптимальным выбором станет использование объектно-ориентированного подхода на базе классов 1С++ и TurboBL, что позволит писать чистый, поддерживаемый и быстродействующий код.