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

Публикация № 893818

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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




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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

 

4

Скачать файлы

Наименование Файл Версия Размер
Демонстрационная конфигурация
.cf 20,04Kb
28.08.18
1
.cf 1.0.0.0 20,04Kb 1 Скачать

См. также

Специальные предложения

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

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

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

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

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