Имитация наследования для методов документов.

28.08.18

Разработка - Универсальные функции

Если Вы делали несколько похожих документов, то Вам приходилось раз за разом вставлять одни и те же обработчики в стандартные процедуры (ПриСозданииНаСервере, ПриОткрытии, ПередЗаписью и т. д.). Даже если они уже убраны в общие модули, с течением времени их может стать много - подсистемы печати, доп. отчетов и обработок, заполнение по умолчанию, контроль активности пользователей, APDEX и еще много чего может быть придумано. С течением времени махина становится неповоротливой, а пользователи жалуются что в разных документах все работает по разному. Один из вариантов решения проблемы - под катом.

Файлы

ВНИМАНИЕ: Файлы из Базы знаний - это исходный код разработки. Это примеры решения задач, шаблоны, заготовки, "строительные материалы" для учетной системы. Файлы ориентированы на специалистов 1С, которые могут разобраться в коде и оптимизировать программу для запуска в базе данных. Гарантии работоспособности нет. Возврата нет. Технической поддержки нет.

Наименование Скачано Купить файл
Демонстрационная конфигурация
.cf 20,04Kb ver:1.0.0.0
1 1 850 руб. Купить

Подписка PRO — скачивайте любые файлы со скидкой до 85% из Базы знаний

Оформите подписку на компанию для решения рабочих задач

Оформить подписку и скачать решение со скидкой

В большинстве современных языков программирования подобные проблемы решаются с помощью наследования, когда наследующий объект получает все свойства родителя. В 1С наследования по умолчанию нет. Но мы же программисты, и ничто не запрещает нам это построить! В данной статье я предлагаю одну из возможных реализаций. Она кажется мне достаточно простой и применимой на практике.

Ядром механизма будет функция

Функция УниверсальныйОбработчик(Источник, ИмяСобытия, Отказ = Ложь, ДополнительныеПараметры = Неопределено) Экспорт

Эту процедуру нужно вставить в соответствующие стандартные процедуры, например так:

&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
	ЭНГ_МодельООП.УниверсальныйОбработчик(ЭтаФорма, "ПриСозданииНаСервере");
КонецПроцедуры

При вызове эта процедура опросит дерево классов, задаваемое в модуле МодельООП. Например, так:

Функция ДеревоКлассов()
	СтруктураКлассов = Новый Структура;
	
	ДобавитьДеревоКлассовРеализации(СтруктураКлассов);
	
	Возврат СтруктураКлассов;
КонецФункции

Функция ДобавитьДеревоКлассовРеализации(Структура)
	
	Структура.Вставить("ДокументРеализации","");

    Структура.Вставить("РеализацияТоваровУслуг", "ДокументРеализации");
    Структура.Вставить("КорректировкаРеализации", "ДокументРеализации");
    
КонецФункции

Таким образом мы указали, что документы (в общем случае любые объекты; но документ и справочник с одинаковым именем будут одним классом) РеализацияТоваровУслуг и КорректировкаРеализации наследуются от родительского класса "ДокументРеализации" (не являющегося объектом метаданных), который является корневым элементом и не наследуется ни от чего.

Далее необходимо будет задать обработчики, которые мы хотим выполнить. Сделать это можно в процедуре СтрукутраОпераций. Например так:

Функция СтруктураОпераций(ИмяКласса, ИмяСобытия)
	СтруктураОпераций = Новый Структура;
	
	Если ИмяКласса = "ДокументРеализации" Тогда
		Возврат СтруктураОперацийДокументаРеализации(ИмяСобытия);
	КонецЕсли;
	
	Если ИмяКласса = "КорректировкаРеализации" Тогда
		Возврат СтруктураОперацийКорректировкиРеализации(ИмяСобытия);
	КонецЕсли;
	
	Возврат СтруктураОпераций;
КонецФункции

Функция СтруктураОперацийДокументаРеализации(ИмяСобытия)
	СтруктураОпераций = Новый Структура;
	
	Если ИмяСобытия = "ПриСозданииНаСервере" Тогда
		
		СтруктураОпераций.Вставить("ПриветИнфостарт",
		НовыйОбработчикСобытия("ЭНГ_Общее.ПриветИнфостарт()"));
		
		СтруктураОпераций.Вставить("УстановкаДаты",
		НовыйОбработчикСобытия("ЭНГ_Общее.УстановитьДатуДокумента(Источник)",,));
		
		СтруктураОпераций.Вставить("СообщениеСДатой",
		НовыйОбработчикСобытия("ЭНГ_Общее.СообщитьДатуДокумента(Источник)",,10));
	КонецЕсли;
	
	Возврат СтруктураОпераций;
КонецФункции

Функция СтруктураОперацийКорректировкиРеализации(ИмяСобытия)
	СтруктураОпераций = Новый Структура;
	
	Если ИмяСобытия = "ПриСозданииНаСервере" Тогда
		
		СтруктураОпераций.Вставить("ПриветИнфостарт",
		НовыйОбработчикСобытия("ЭНГ_Общее.ПриветИнфостарт()",-10));
		
	КонецЕсли;
	
	Возврат СтруктураОпераций;

КонецФункции




//В модуле ЭНГ_Общее

Процедура УстановитьДатуДокумента(Форма) Экспорт
    Форма.Объект.Дата = ТекущаяДата();
КонецПроцедуры

Процедура ПриветИнфостарт() Экспорт
    Сообщить("Привет Инфостарт");
КонецПроцедуры

Процедура СообщитьДатуДокумента(Форма) Экспорт
	Сообщить(""+Форма.Объект.Дата);
КонецПроцедуры


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

В приложенных файлах - конфигурация, демонстрирующая механизм. Кроме того, прикладываю полный листинг основного модуля:

#Область НастройкиООП

Функция СтруктураОпераций(ИмяКласса, ИмяСобытия)
	СтруктураОпераций = Новый Структура;
	
	Если ИмяКласса = "ДокументРеализации" Тогда
		Возврат СтруктураОперацийДокументаРеализации(ИмяСобытия);
	КонецЕсли;
	
	Если ИмяКласса = "КорректировкаРеализации" Тогда
		Возврат СтруктураОперацийКорректировкиРеализации(ИмяСобытия);
	КонецЕсли;
	
	Возврат СтруктураОпераций;
КонецФункции

//Функция возвращает дерево классов. Дерево представляет из себя структуру { Ключ: ИмяКласса, Значение: ИмяРодителя }.
//Если у класса нет родителя, ИмяРодителя должно быть пустой строкой.
//Классы бывают двух типов - объектные и виртуальные. 
//Объектные классы соответствуют объектам метаданных. При вызове Универссального Обработчика он определяет, какой класс его вызвал по метаданным переданного источника.
//Виртуальные классы - это произвольные строки. Они предназначены для группировки схожих Объектных классов.
//Дерево классов используется для определения структур операций.
Функция ДеревоКлассов()
	СтруктураКлассов = Новый Структура;
	
	ДобавитьДеревоКлассовРеализации(СтруктураКлассов);
	
	Возврат СтруктураКлассов;
КонецФункции

#КонецОбласти

#Область ДвижокООП
//Это входная точка движка. Данная процедура размещается в коде объектов.
//На вход необходимо передавать:
//Источник: ЭтаФорма при размещении в модуле формы, ЭтотОбъект при размещении в модуле объекта.
//ИмяСобытия: Имя процедуры, в которой размещен обработчик. Например, "ПриСозданииНаСервере".
//Отказ: если у процедуры, в которой размещен универсальный обработчик, есть параметр "Отказ", его можно передать сюда.
//В этом случае его можно будет использовать в Обработчиках Событий.
//В ходе выполнения функция получит все операции в соответствии с деревом классов и выполнит те, у которых неотрицательный приоритет.
Функция УниверсальныйОбработчик(Источник, ИмяСобытия, Отказ = Ложь, ДополнительныеПараметры = Неопределено) Экспорт
	ИмяКласса = ЭНГ_ОбщееСервер.ИмяМетаданных(ЭНГ_Формы.ИсточникДанных(Источник).Ссылка);
	ДеревоКлассов = ДеревоКлассов();
	СтруктураОпераций = СтруктураОперацийСНаследованием(ИмяКласса, ИмяСобытия, ДеревоКлассов);
	//Для сортировки операций, чтобы выполнять их в правильном порядке, используем сортировку списка значений.
	//Не используем таблицу потому что она недоступна на клиенте.
	//Эта же процедура убирает операции с отрицательным приоритетом, которые не нужно выполнять.
	УпорядоченныеОперации = УпорядоченныеОперации(СтруктураОпераций);
	Если УпорядоченныеОперации.Количество() = 0 Тогда
		ЭНГ_Общее.СлужебноеСообщение(
		"Модель ООП: предупреждение - для класса " + ИмяКласса + " и события " + ИмяСобытия + 
		" не определены выполняемые обработчики! Это может являться ошибкой"); 
	КонецЕсли;
	Для Каждого Элемент Из УпорядоченныеОперации Цикл
		Попытка
			Выполнить(СтруктураОпераций[Элемент.Представление].Код);
		Исключение
			ОписаниеОшибки = ОписаниеОшибки();
		КонецПопытки;
		Если Отказ Тогда
			Возврат Неопределено;
		КонецЕсли;
	КонецЦикла;
	
КонецФункции

//Эта процедура формирует список операций согласно дереву классов.
//При выполнении она идет вверх по дереву классов до тех пор, пока не находит класс без родителя.
//Этот класс считается корневым; функция получает для него структуру операций, где Ключ: ИмяОперации, Значение: ОбработчикСобытия(Структура).
//Дальше функция идет вниз по дереву классов, последовательно модифицируя список операций.
//Для каждого дочернего узла дерева получаем его структуру операций. Если её нет в структуре операций родителя, тогда добавляем.
//Если модифицируемая Операция уже есть в структуре операций, тогда сравниваем приоритет по модулю. Если приоритет новой операции ниже, тогда игнорируем эту модификацию.
//В противном случае полностью заменяем старый ОбработчикСобытия на новый.
Функция СтруктураОперацийСНаследованием(ИмяКласса, ИмяСобытия, ДеревоКлассов)
	Перем ИмяРодителя;
	Если НЕ ДеревоКлассов.Свойство(ИмяКласса, ИмяРодителя) Тогда
		Сообщить("Объект " + ИмяКласса + " обращается к механизму ООП, но не включен в иерархию классов! Необходимо добавить соответствующую запись в структуру, возвращаемую функцией ДеревоКлассов()");
		Возврат Новый Структура;
	КонецЕсли;
	Если ИмяРодителя = "" Тогда
		Возврат СтруктураОпераций(ИмяКласса, ИмяСобытия);
	КонецЕсли;
	СтруктураОперацийРодителя = СтруктураОперацийСНаследованием(ИмяРодителя, ИмяСобытия, ДеревоКлассов);
	СтруктураОпераций = СтруктураОпераций(ИмяКласса, ИмяСобытия);
	Для Каждого Элемент Из СтруктураОпераций Цикл
		Если НЕ СтруктураОперацийРодителя.Свойство(Элемент.Ключ) Тогда
			СтруктураОперацийРодителя.Вставить(Элемент.Ключ, Элемент.Значение);
			Продолжить;
		КонецЕсли;
		Если ЭНГ_Числа.Модуль(СтруктураОперацийРодителя[Элемент.Ключ].Приоритет) > ЭНГ_Числа.Модуль(Элемент.Значение.Приоритет) Тогда
			Продолжить;
		КонецЕсли;
		ЗаполнитьЗначенияСвойств(СтруктураОперацийРодителя[Элемент.Ключ], Элемент.Значение);
	КонецЦикла;
	Возврат СтруктураОперацийРодителя;
	
КонецФункции

//Создает структуру ОбработчикСобытия.
//Код: программный код, который будет выполнен через УниверсальныйОбработчик.
//Приоритет: величина по модулю определяет, насколько критиченм этот вариант по сравнению с другими. Знак определяет выполнение - если Приоритет отрицателен, ОбработчикСобытия выполнен не будет. 
//Порядок: определяет порядок выполнения, от меньшего к большему. Если порядки одинаковы, ОбработчикиСобытия выполнятся "как получится". 
Функция НовыйОбработчикСобытия(Код, Приоритет = 1, Порядок = 1)
	
	Структура = Новый Структура;
	
	Структура.Вставить("Код", Код);
	Структура.Вставить("Приоритет", Приоритет);
	Структура.Вставить("Порядок", Порядок);
	
	Возврат Структура;
	
КонецФункции

Функция УпорядоченныеОперации(СтруктураОпераций)
	СписокИменОпераций = Новый СписокЗначений;
	Для Каждого Элемент Из СтруктураОпераций Цикл
		Если Элемент.Значение.Приоритет < 0 Тогда
			Продолжить;
		КонецЕсли;
		СписокИменОпераций.Добавить(Элемент.Значение.Порядок,Элемент.Ключ);
	КонецЦикла;
	СписокИменОпераций.СортироватьПоЗначению();
	Возврат СписокИменОпераций;	
КонецФункции

#КонецОбласти

#Область ПрикладнойКод

Функция ДобавитьДеревоКлассовРеализации(Структура)
	
	Структура.Вставить("ДокументРеализации","");

    Структура.Вставить("РеализацияТоваровУслуг", "ДокументРеализации");
    Структура.Вставить("КорректировкаРеализации", "ДокументРеализации");
    
КонецФункции

Функция СтруктураОперацийДокументаРеализации(ИмяСобытия)
	СтруктураОпераций = Новый Структура;
	
	Если ИмяСобытия = "ПриСозданииНаСервере" Тогда
		
		СтруктураОпераций.Вставить("ПриветИнфостарт",
		НовыйОбработчикСобытия("ЭНГ_Общее.ПриветИнфостарт()"));
		
		СтруктураОпераций.Вставить("УстановкаДаты",
		НовыйОбработчикСобытия("ЭНГ_Общее.УстановитьДатуДокумента(Источник)",,));
		
		СтруктураОпераций.Вставить("СообщениеСДатой",
		НовыйОбработчикСобытия("ЭНГ_Общее.СообщитьДатуДокумента(Источник)",,10));
	КонецЕсли;
	
	Возврат СтруктураОпераций;
КонецФункции

Функция СтруктураОперацийКорректировкиРеализации(ИмяСобытия)
	СтруктураОпераций = Новый Структура;
	
	Если ИмяСобытия = "ПриСозданииНаСервере" Тогда
		
		СтруктураОпераций.Вставить("ПриветИнфостарт",
		НовыйОбработчикСобытия("ЭНГ_Общее.ПриветИнфостарт()",-10));
		
	КонецЕсли;
	
	Возврат СтруктураОпераций;

КонецФункции

#КонецОбласти

 

Вступайте в нашу телеграмм-группу Инфостарт

ООП практика программирования

См. также

Универсальные функции Работа с интерфейсом Программист 1С v8.3 Бесплатно (free)

Порой необходимо временно отключить расширение 1С, не удаляя его, чтобы не потерять данные. Но в этом случае при каждом запуске всем будет лезть уведомление о неактивном расширении, хотя очевидно, это техническая информация, которой не стоит лишний раз пугать пользователей.

14.05.2025    4994    DeerCven    14    

57

Универсальные функции Программист 1С v8.3 1C:Бухгалтерия Бесплатно (free)

Благодаря этим пяти строчкам можно больше не заморачиваться с загрузкой из внешних файлов. Пользуюсь везде, всегда и постоянно.

21.05.2024    45223    dimanich70    83    

161

Универсальные функции Программист 1С v8.3 1C:Бухгалтерия Абонемент ($m)

Задача: вставить картинку из буфера обмена на форму средствами платформы 1С.

1 стартмани

18.03.2024    6698    6    John_d    13    

59

Универсальные функции Программист Стажер 1С v8.3 1C:Бухгалтерия Бесплатно (free)

Пришлось помучиться с GUID-ами немного, решил поделиться опытом, мало ли кому пригодится.

12.02.2024    55778    atdonya    31    

68

Универсальные функции Программист 1С v8.3 Бесплатно (free)

На заключительных этапах, когда идет отладка или доработка интерфейса, необходимо много раз переоткрыть внешний объект. Вот один из способов автоматизации этого.

30.11.2023    8441    ke.92@mail.ru    17    

67

WEB-интеграция Универсальные функции Механизмы платформы 1С Программист 1С v8.3 1C:Бухгалтерия Бесплатно (free)

При работе с интеграциями рано или поздно придется столкнуться с получением JSON файлов. И, конечно же, жизнь заставит проверять файлы перед тем, как записывать данные в БД.

28.08.2023    22386    YA_418728146    8    

174
Комментарии
Подписаться на ответы Инфостарт бот Сортировка: Древо развёрнутое
Свернуть все
1. CyberCerber 892 28.08.18 17:01 Сейчас в теме
"При вызове эта процедура опросит дерево классов, задаваемое в модуле МодельООП. Например, так:"
Кажется, картинка не вставилась
2. Enigma 313 28.08.18 17:11 Сейчас в теме
3. CyberCerber 892 28.08.18 17:13 Сейчас в теме
Идея понятна, но в итоге получается какой-то спагетти-код. ООП создан для упрощения работы со сложными системами, а тут если будет больше 10 документов в иерархии, уже черт ногу сломит.
Тут для красоты и удобства надо разбивать классы на модули, совпадающие с названием класса.
И использовать оператор Выполнить, хоть это и официально не рекомендуется.
В итоге будет не куча Если, а что-то типа Выполнить(ИмяКласса + "." + ИмяМетода + "(Параметры)")
RustIG; albert.goncharov; o.nikolaev; +3 Ответить
5. albert.goncharov 88 01.09.18 16:03 Сейчас в теме
(3) Логичней как в БСП - имена методов в базовом классе известны заранее, и вызов идёт
Модуль.ИмяМетода(Параметры);
А Модуль получается предварительно из имени модуля с помощью Вычислить(ИмяМодуля)
4. Enigma 313 28.08.18 17:26 Сейчас в теме
Я думал о том, что все, связанное со специфическим классом, должно быть или в модуле менеджера соответствующего объекта метаданных, или в общем модуле. Но для решения первоначальной задачи, ради которой был построен механизм, больше подходило хранение всего в одном месте (16 классов, но максимальная глубина наследования только 2; итоговая структура получается вполне читаемая).

Выполнить используется в любом случае. К сожалению, 1С не дает возможности передать функцию в качестве переменной.
6. пользователь 26.03.19 14:26
Сообщение было скрыто модератором.
...
7. kote 537 07.07.19 16:36 Сейчас в теме
Наследование - зло и довольно быстро "цементирует" приложение лишая возможности внесения изменений

Используйте композицию - тот же момент наступит сильно позже

Код будет чище и проще для понимания

Потом, интересно было бы посмотреть как бы Вы предложили эмулировать миксины. По моему они без проблем могут быть встроены в вашу подсистему.
Для отправки сообщения требуется регистрация/авторизация