Что делает метод ПродолжитьВызов() в расширениях 1С с аннотацией &Вместо?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Коллеги, сегодня мы подробно разберем один из ключевых механизмов расширений в 1С:Предприятие 8.3 — метод ПродолжитьВызов(), используемый в связке с аннотацией &Вместо. Часто у разработчиков возникает вопрос, как именно эти два инструмента взаимодействуют и какой результат мы получаем на выходе. Мы рассмотрим это на конкретном примере, приведенном в исходной теме.

Представим, что у нас есть процедура в основной конфигурации:


Процедура ВыполнитьПроверкуСтруктурыОбменаНаВалидность(СтруктураНастроекОбмена, ИспользоватьНастройкиТранспорта = Истина) Экспорт
	
	УзелИнформационнойБазы = СтруктураНастроекОбмена.УзелИнформационнойБазы; // ПланОбменаСсылка
	
	Если НЕ ЗначениеЗаполнено(УзелИнформационнойБазы) Тогда
		
		// Узел информационной базы не должен быть пустым.
		СтрокаСообщенияОбОшибке = НСтр(
		"ru = 'Не задан узел информационной базы, с которой нужно производить обмен информацией. Обмен отменен.'",
			ОбщегоНазначения.КодОсновногоЯзыка());
		ЗаписьЖурналаРегистрацииОбменаДанными(СтрокаСообщенияОбОшибке, СтруктураНастроекОбмена, Истина);
		ЗафиксироватьЗавершениеИнициализацииОбмена(СтруктураНастроекОбмена);
		
	КонецЕсли;
	// ... Продолжение кода процедуры ...
КонецПроцедуры

Мы решили доработать эту процедуру с помощью расширения, используя аннотацию &Вместо. Вот как выглядит наша модифицированная процедура в расширении:


&Вместо("ВыполнитьПроверкуСтруктурыОбменаНаВалидность")
Процедура Обмен_ВыполнитьПроверкуСтруктурыОбменаНаВалидность(СтруктураНастроекОбмена, ИспользоватьНастройкиТранспорта)
	УзелИнформационнойБазы = СтруктураНастроекОбмена.УзелИнформационнойБазы; // ПланОбменаСсылка
	
	Если НЕ ЗначениеЗаполнено(УзелИнформационнойБазы) Тогда
		
		// Мы закомментировали типовую логику обработки ошибки
		//СтрокаСообщенияОбОшибке = НСтр(
		//       "ru = 'Не задан узел информационной базы, с которой нужно производить обмен информацией. Обмен отменен.'",
		//	 ОбщегоНазначения.КодОсновногоЯзыка());
		//ЗаписьЖурналаРегистрацииОбменаДанными(СтрокаСообщенияОбОшибке, СтруктураНастроекОбмена, Истина);
		//ЗафиксироватьЗавершениеИнициализацииОбмена(СтруктураНастроекОбмена);
		
	КонецЕсли;
    ПродолжитьВызов(СтруктураНастроекОбмена, ИспользоватьНастройкиТранспорта);
	// ... Возможно, здесь есть дополнительная логика после вызова ...
КонецПроцедуры

Наш вопрос заключается в том, что именно делает ПродолжитьВызов(СтруктураНастроекОбмена, ИспользоватьНастройкиТранспорта); в данном контексте, ведь мы уже использовали аннотацию &Вместо, которая, казалось бы, полностью заменяет исходную процедуру.

Понимание аннотации &Вместо

Прежде чем перейти к ПродолжитьВызов(), давайте четко определим, что означает &Вместо. Когда мы используем &Вместо("ИмяПроцедуры"), мы говорим платформе 1С, что наша процедура в расширении (например, Обмен_ВыполнитьПроверкуСтруктурыОбменаНаВалидность) должна быть выполнена вместо оригинальной процедуры с тем же именем в основной конфигурации (ВыполнитьПроверкуСтруктурыОбменаНаВалидность). Это означает, что если кто-то вызывает ВыполнитьПроверкуСтруктурыОбменаНаВалидность, на самом деле будет вызвана наша процедура из расширения.

Без использования ПродолжитьВызов() внутри процедуры, помеченной &Вместо, оригинальный код основной конфигурации никогда не будет выполнен. Это ключевой момент, который нужно усвоить. Аннотация &Вместо дает нам полный контроль над выполнением исходной процедуры.

Назначение и принцип работы ПродолжитьВызов()

Метод ПродолжитьВызов() – это наш мост к оригинальному коду. Он предназначен для однократного вызова переопределенного метода или обработчика события из расширяемой конфигурации. Другими словами, когда мы находимся внутри нашей процедуры, помеченной &Вместо, вызов ПродолжитьВызов() передает управление к той самой оригинальной процедуре, которую мы "заместили".

Если проводить аналогии с объектно-ориентированным программированием (например, C# или Java), то ПродолжитьВызов() ведет себя подобно вызову родительского метода через ключевое слово base или super. Мы переопределили метод, но хотим, чтобы в какой-то момент выполнилась и логика, заложенная в родительском классе.

Таким образом, когда мы видим:


&Вместо("ВыполнитьПроверкуСтруктурыОбменаНаВалидность")
Процедура Обмен_ВыполнитьПроверкуСтруктурыОбменаНаВалидность(СтруктураНастроекОбмена, ИспользоватьНастройкиТранспорта)
    // ... наша логика до вызова оригинальной процедуры ...
    ПродолжитьВызов(СтруктураНастроекОбмена, ИспользоватьНастройкиТранспорта);
    // ... наша логика после вызова оригинальной процедуры ...
КонецПроцедуры

Происходит следующее:

  1. Вызывается оригинальная процедура ВыполнитьПроверкуСтруктурыОбменаНаВалидность.
  2. Платформа 1С обнаруживает расширение с аннотацией &Вместо и вместо оригинальной процедуры передает управление нашей процедуре Обмен_ВыполнитьПроверкуСтруктурыОбменаНаВалидность.
  3. Внутри нашей процедуры выполняется весь код, который мы написали до вызова ПродолжитьВызов() (в нашем случае это проверка Если НЕ ЗначениеЗаполнено(УзелИнформационнойБазы) Тогда, но без типовой обработки ошибки).
  4. Когда управление доходит до строки ПродолжитьВызов(СтруктураНастроекОбмена, ИспользоватьНастройкиТранспорта);, платформа временно передает управление обратно оригинальной процедуре ВыполнитьПроверкуСтруктурыОбменаНаВалидность.
  5. Оригинальная процедура выполняется до конца.
  6. После завершения оригинальной процедуры, управление возвращается обратно в нашу процедуру расширения, к строке, следующей за ПродолжитьВызов(). Здесь мы можем добавить дополнительную логику, которая должна отработать после типовой.

Разбираем ваш пример

Вернемся к вашему коду:


&Вместо("ВыполнитьПроверкуСтруктурыОбменаНаВалидность")
Процедура Обмен_ВыполнитьПроверкуСтруктурыОбменаНаВалидность(СтруктураНастроекОбмена, ИспользоватьНастройкиТранспорта)
	УзелИнформационнойБазы = СтруктураНастроекОбмена.УзелИнформационнойБазы; // ПланОбменаСсылка
	
	Если НЕ ЗначениеЗаполнено(УзелИнформационнойБазы) Тогда
		
		// Здесь вы закомментировали типовую логику обработки ошибки
		//СтрокаСообщенияОбОшибке = НСтр(...);
		//ЗаписьЖурналаРегистрацииОбменаДанными(...);
		//ЗафиксироватьЗавершениеИнициализацииОбмена(...);
		
	КонецЕсли;
    ПродолжитьВызов(СтруктураНастроекОбмена, ИспользоватьНастройкиТранспорта);
КонецПроцедуры

Что здесь произойдет:

  1. При вызове ВыполнитьПроверкуСтруктурыОбменаНаВалидность будет выполнена ваша процедура из расширения.
  2. Ваша процедура проверит Если НЕ ЗначениеЗаполнено(УзелИнформационнойБазы) Тогда.
  3. Если узел не заполнен, блок Тогда будет выполнен, но поскольку вы закомментировали типовые строки, никаких сообщений об ошибке или записей в журнал регистрации сделано не будет на этом этапе.
  4. Затем будет вызван ПродолжитьВызов(СтруктураНастроекОбмена, ИспользоватьНастройкиТранспорта);.
  5. Управление перейдет к оригинальной процедуре из основной конфигурации.
  6. Оригинальная процедура снова выполнит проверку Если НЕ ЗначениеЗаполнено(УзелИнформационнойБазы) Тогда. Поскольку это тот же объект СтруктураНастроекОбмена, условие снова окажется истинным, если УзелИнформационнойБазы не заполнен.
  7. Именно в этот момент будут выполнены строки, которые вы закомментировали в расширении: будет сформировано сообщение об ошибке, оно будет записано в журнал регистрации, и будет зафиксировано завершение инициализации обмена.
  8. Оригинальная процедура завершится, и управление вернется в вашу процедуру расширения, но так как после ПродолжитьВызов() больше нет кода, ваша процедура также завершится.

Таким образом, в вашем случае ПродолжитьВызов() гарантирует, что типовая логика обработки ошибки все равно будет выполнена, несмотря на то, что вы ее закомментировали в своей процедуре расширения. Вы, по сути, обернули типовой код в свою процедуру, но не заменили его полностью, а лишь добавили возможность выполнения дополнительной логики (или ее удаления) до или после типового вызова.

Различные сценарии использования ПродолжитьВызов()

Метод ПродолжитьВызов() предоставляет широкие возможности для гибкой модификации поведения типовых процедур и функций. Рассмотрим подробнее несколько распространенных сценариев.

1. Просто вызов исходной процедуры (без дополнительной логики)

Если мы хотим, чтобы процедура в расширении просто "пропускала" вызов в основную конфигурацию, не добавляя никакой собственной логики, мы можем сделать это следующим образом:


&Вместо("ВыполнитьПроверкуСтруктурыОбменаНаВалидность")
Процедура Обмен_ВыполнитьПроверкуСтруктурыОбменаНаВалидность(СтруктураНастроекОбмена, ИспользоватьНастройкиТранспорта)
    ПродолжитьВызов(СтруктураНастроекОбмена, ИспользоватьНастройкиТранспорта);
КонецПроцедуры

В этом случае, как вы и предположили в Сообщении 4, исполнится просто код Основной конфигурации, и всё. Это может быть полезно, если мы хотим временно "отключить" какую-то нашу логику в расширении, но при этом обеспечить выполнение типового кода.

2. Логика "до" и "после" вызова исходной процедуры

Это один из самых частых и мощных сценариев. Мы можем выполнить нашу дополнительную логику до вызова типового метода, передать измененные параметры в типовой метод (если это функция или процедура с изменяемыми параметрами) и затем выполнить логику после его завершения.


&Вместо("ВыполнитьПроверкуСтруктурыОбменаНаВалидность")
Процедура Обмен_ВыполнитьПроверкуСтруктурыОбменаНаВалидность(СтруктураНастроекОбмена, ИспользоватьНастройкиТранспорта)
    // Логика ДО вызова типовой процедуры:
    // Например, дополнительная проверка или модификация входных данных
    Если НЕ СтруктураНастроекОбмена.ДополнительнаяНастройкаЗаполнена Тогда
        // Запишем предупреждение в журнал, но не будем прерывать выполнение
        ЗаписьЖурналаРегистрации("Обмен.Проверка", УровеньЖурналаРегистрации.Предупреждение, , , "Дополнительная настройка не заполнена.");
    КонецЕсли;

    // Вызов типовой процедуры
    ПродолжитьВызов(СтруктураНастроекОбмена, ИспользоватьНастройкиТранспорта);

    // Логика ПОСЛЕ вызова типовой процедуры:
    // Например, анализ результата, дополнительная запись в журнал или модификация поведения
    Если СтруктураНастроекОбмена.ОбменЗавершенУспешно Тогда
        ЗаписьЖурналаРегистрации("Обмен.Проверка", УровеньЖурналаРегистрации.Информация, , , "Проверка структуры обмена успешно завершена (из расширения).");
    Иначе
        ЗаписьЖурналаРегистрации("Обмен.Проверка", УровеньЖурналаРегистрации.Ошибка, , , "Проверка структуры обмена завершена с ошибками (из расширения).");
    КонецЕсли;
КонецПроцедуры

Здесь мы видим, как наша процедура "обертывает" типовую, позволяя нам контролировать процесс выполнения.

3. Условный вызов исходной процедуры

Мы имеем возможность решить, нужно ли вызывать типовую процедуру вообще. Это дает максимальную гибкость, но требует осторожности.


&Вместо("ВыполнитьПроверкуСтруктурыОбменаНаВалидность")
Процедура Обмен_ВыполнитьПроверкуСтруктурыОбменаНаВалидность(СтруктураНастроекОбмена, ИспользоватьНастройкиТранспорта)
    УзелИнформационнойБазы = СтруктураНастроекОбмена.УзелИнформационнойБазы;

    Если НЕ ЗначениеЗаполнено(УзелИнформационнойБазы) Тогда
        // Мы решили полностью заменить типовую логику обработки ошибки
        СтрокаМоегоСообщения = "Необходимо указать узел ИБ для обмена. Моя обработка ошибки.";
        Сообщить(СтрокаМоегоСообщения);
        ЗафиксироватьЗавершениеИнициализацииОбмена(СтруктураНастроекОбмена); // Возможно, мы хотим вызвать только эту часть типовой логики или свою
        Возврат; // Полностью прерываем выполнение и не вызываем ПродолжитьВызов()
    КонецЕсли;

    // Если узел заполнен, то вызываем типовую логику
    ПродолжитьВызов(СтруктураНастроекОбмена, ИспользоватьНастройкиТранспорта);
КонецПроцедуры

В этом примере, если УзелИнформационнойБазы не заполнен, мы полностью перехватываем обработку ошибки, выводим свое сообщение и не вызываем ПродолжитьВызов(). Это означает, что типовая логика формирования сообщения и записи в журнал регистрации, присутствующая в основной конфигурации, не будет выполнена. Мы полностью заменили ее своей. Это мощный инструмент, но он несет риски: при обновлении конфигурации мы можем пропустить важные изменения в типовой логике.

4. Использование ПродолжитьВызов() с функциями

Когда мы работаем не с процедурами, а с функциями, ПродолжитьВызов() становится еще более актуальным, так как он позволяет нам получить возвращаемое значение типовой функции и модифицировать его.


// Предположим, в основной конфигурации есть такая функция
// Функция ПолучитьСписокДоступныхЭлементов(ПараметрыОтбора) Экспорт
//    Список = Новый СписокЗначений();
//    // ... заполнение списка ...
//    Возврат Список;
// КонецФункции

&Вместо("ПолучитьСписокДоступныхЭлементов")
Функция Обмен_ПолучитьСписокДоступныхЭлементов(ПараметрыОтбора)
    // Сначала получаем типовой список
    Список = ПродолжитьВызов(ПараметрыОтбора);

    // Затем модифицируем его нашей логикой
    Список.Добавить("МойДополнительныйЭлемент", "ДопЭлемент");

    Возврат Список;
КонецФункции

В этом случае мы сначала получаем результат работы типовой функции, затем добавляем к нему свои значения и только потом возвращаем итоговый модифицированный список. Это очень наглядно демонстрирует мощь ПродолжитьВызов(), как было отмечено в Сообщении 2.

Сравнение с аннотациями &Перед и &После

Для полноты картины давайте сравним &Вместо в сочетании с ПродолжитьВызов() с другими популярными аннотациями расширений: &Перед и &После.

  1. &Перед: Процедура расширения выполняется до типовой процедуры. При этом типовая процедура всегда вызывается автоматически после завершения кода в расширении. Мы не можем предотвратить ее выполнение.
  2. &После: Процедура расширения выполняется после типовой процедуры. Типовая процедура также всегда вызывается автоматически до начала выполнения кода в расширении. Мы не можем предотвратить ее выполнение.
  3. &Вместо с ПродолжитьВызов(): Как мы выяснили, наша процедура расширения полностью замещает типовую. Вызов ПродолжитьВызов() внутри нашей процедуры — это наше осознанное решение выполнить типовой код. Если мы не вызовем ПродолжитьВызов(), типовой код не будет выполнен.

Выбор между этими подходами зависит от задачи:

Нюансы и рекомендации

  1. Однократный вызов: Метод ПродолжитьВызов() предназначен для однократного вызова. Повторный вызов в рамках одной и той же замещающей процедуры приведет к ошибке.
  2. Передача параметров: При вызове ПродолжитьВызов() необходимо передать все те же параметры, которые были получены в процедуру расширения. Рекомендуется называть параметры в методе-перехватчике так же, как и в методе расширяемой конфигурации для ясности.
  3. Совместимость расширений: Если несколько расширений используют &Вместо с безусловным вызовом ПродолжитьВызов(), платформа 1С корректно обработает эту цепочку. Каждое расширение вызовет следующее в цепочке, пока не дойдет до оригинального метода. Однако, если одно из расширений условно не вызывает ПродолжитьВызов(), оно может прервать цепочку для других расширений.
  4. Области применения: Механизм расширений, включая &Вместо и ПродолжитьВызов(), позволяет дорабатывать практически любые модули конфигурации, кроме модулей обычных форм. Это могут быть общие модули, модули объектов, модули менеджеров, модули форм и т.д.
  5. Аккуратность с условным вызовом: Будьте крайне осторожны, если принимаете решение не вызывать ПродолжитьВызов() в определенных условиях. Вы фактически полностью перехватываете контроль и должны быть готовы к тому, что будущие обновления конфигурации могут изменить или улучшить типовую логику, которую вы решили проигнорировать.

Итак, мы видим, что ПродолжитьВызов() — это мощный и гибкий инструмент в арсенале разработчика 1С. Он позволяет нам не только модифицировать поведение типовых процедур и функций, но и сохранять возможность их выполнения, что критически важно для поддержки и развития конфигураций. Мы не просто "заменяем" код, а "расширяем" его, встраивая свою логику до, после или даже вместо типовой, но с возможностью явного контроля над ее вызовом.

← На главную