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

28.08.18

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

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

Скачать файл

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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




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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

 

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

См. также

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

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

21.05.2024    20235    dimanich70    81    

145

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

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

1 стартмани

18.03.2024    4100    3    John_d    11    

57

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

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

12.02.2024    18194    atdonya    24    

57

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

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

30.11.2023    5508    ke.92@mail.ru    16    

65

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

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

28.08.2023    14774    YA_418728146    7    

166

Пакетная печать Печатные формы Адаптация типовых решений Универсальные функции Платформа 1С v8.3 1С:ERP Управление предприятием 2 1С:Управление торговлей 11 1С:Комплексная автоматизация 2.х Россия Абонемент ($m)

Расширение для программ 1С:Управление торговлей, 1С:Комплексная автоматизация, 1С:ERP, которое позволяет распечатывать печатные формы для непроведенных документов. Можно настроить, каким пользователям, какие конкретные формы документов разрешено печатать без проведения документа.

2 стартмани

22.08.2023    3593    57    progmaster    8    

4

Инструментарий разработчика Универсальные функции Платформа 1С v8.3 Конфигурации 1cv8 1С:Розница 2 1С:ERP Управление предприятием 2 1С:Бухгалтерия 3.0 1С:Управление торговлей 11 1С:Комплексная автоматизация 2.х 1С:Зарплата и Управление Персоналом 3.x Абонемент ($m)

Копирует в буфер значения из списков, из ячеек отчетов, таблиц, настроек списков, других отборов и вставляет в выбранную настройку отбора. Работает с Объект не найден. Работает как в одной так и между разными базами 1С. Использует комбинации [Alt+C] Копировать список, [Alt+V] Вставить список. Также для копирования данных используется стандартная [Ctrl+C] (например из открытого xls, mxl, doc и т.п. файла скопировать список наименований)

1 стартмани

13.10.2022    18498    171    sapervodichka    112    

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

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

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

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

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