ООП - объектно-ориентированное программирование, является на сегодняшний день одной из самых популярных технологий создания программных средств. Данная разработка направлена на устранение недостатка отсутствия ООП в языке 1С. Как известно, ООП базируется на трех основных принципах: полиморфизм, наследование и инкапсуляция; в данной разработке для возможной технической реализации принципов, были добавлены некоторые языковые средства. Также добавлена возможность строгой проверки типов для параметров методов классов, определяемых пользователем (КОП) и некоторые другие классы для расширения парадигмы ООП.
Наследование - это обобщение объектов за счет выведения общего поведения в логически связанных сущностях проекта. Возможность повторно использовать код и отделить интерфейс объекта от его реализации с целью повышения взаимозаменяемости и расширения частей системы без перепрограммирования и дополнительного тестирования множества модулей. Наследование позволяет представить на логическим уровне модель проектирования учетной системы более приближенно к проектируемой области. Наследование необходимо реализовать для достижения следующих целей:
В реализации наследования в ВК (внешней компоненте 1С:Предприятия) 1С++ необходимо определить следующий функционал:
Полиморфизм - заключается в переопределении поведения объекта с помощью специализации обобщенного класса, т.е. переопределение методов интерфейсов базового класса (общий класс) его наследником (более специализированным классом). Полиморфизм позволит:
Инкапсуляция - сокрытие деталей реализации классов за четко определенным интерфейсом. Это позволит разрабатывать компоненты и настраивать их взаимодействие с меньшими зависимостями между ними, что в свою очередь, уменьшит время тестирования и вероятность появления ошибок в алгоритмах программы.
Каждый класс, определенный пользователем (КОП), создается в 1С с помощью метода СоздатьОбъект(ИмяКласса).
Для каждого КОП определен стандартный интерфейс, состоящий из следующих методов:
ПолучитьБазовыйКласс (ИмяБазовогоКласса) / GetBaseClass(strNameOfBaseClass) возвращает объект базового класса для класса предка, имя которого передается в качестве строки в параметр метода "ИмяБазовогоКласса". Если объекта базового класса не существует, то метод возвратит 0. Данный метод предназначен для вызова переопределенных методов базовых классов из методов классов наследников и получения/установки атрибутов базовых классов. Пример: имеются следующие справочники: "Необоротные активы", в котором содержатся общие реквизиты всех необоротных активов предприятия, и справочники - спецификаторы, такие как "Основные средства", "Нематериальные активы", которые, в свою очередь, добавляют специфические реквизиты сущностей к общим реквизитам необоротных активов. Исходя из описанной выше идеологии, мы строим следующую иерархию классов: КОП с рабочим названием "ОС" наследует от классов 1С: справочника "Необоротные активы" и "Основные средства" и переопределяет методы "ВыбратьЭлементы()" и "ПолучитьЭлемент()", т.к у КОП "ОС" базовые классы имеют такие же методы, программист обязан разрешить неоднозначность вызова, воспользовавшись в реализации методов "ВыбратьЭлементы()" и "ПолучитьЭлемент()" класса "ОС" методом ПолучитьБазовыйКласс("Основные средства") для вызова его функций выборки и итераций и ПолучитьБазовыйКласс ("Необоротные активы") для проведения аналогичных операций.
ОтправитьСообщениеМодулюХоз (КтоОтправил, ВидСообщения, Данные) / SendMessageOwnMod(WhoSend, KindMessage, Data). Вызывает предопределенную функцию ОбработкаСобытияОтКласса(отКого, стрСобытие, Данные) реализованную в модуле вызывающем работающий в данный момент метод КОП, возвращает любое значение, которое будет получено после вызова ОтправитьСообщениеМодулюХоз() в модуле КОП.
ПолучитьПуть () / GetPathName(). Возвращает полный путь и название файла, в котором хранится модуль реализации класса.
ПолучитьКонтекстОкружения () / GetEnvContext() - Получить контекст окружения из модуля КОП. Возврат: Возвращает контекст, из которого получил управление модуль КОП Метод считается устаревшим и оставлен только для совместимости. Проблема метода ПолучитьКонтекстОкружения() в том, что класс получает контекст формы, что-то с ним делает, а форма об этом ни сном, ни духом. Причем ситуация усугубляется тем, что ПолучитьКонтекстОкружения() можно вызвать в каком-нибудь из базовых классов. Или в классе может быть вызван метод другого класса, который и вызывает ПолучитьКонтекстОкружения(). Эту цепочку можно удлиннять и комбинировать.
ПолучитьСписокПараметров (стрИмяМетода) / GetParamsList(strNameOfMeth) Получить список со значениями неявных параметров, переданных в метод, название которого необходимо передать в качестве параметра. Данный метод можно использовать только в теле метода класса, который был определен с последним формальным параметром ":" в файле определения КОП. Более подробное описание см. в пункте 1.3., раздел "Неопределенное количество параметров".
УстановитьПараметрПоИндексу (стрИмяМетода>,<чИнд>,<нЗнач) / SetOnIndexParams(strNameOfMeth >,<nInd>,<uVal) - Метод предназначен для получения ссылки значения неявного параметра по его порядковому номеру и установке нового значения по данной ссылке. - стрИмяМетода - Имя метода ссылки на неявные параметры, которого необходимо получить. - чИнд - номер неявного параметра, ссылку на который надо получить и заменить - нЗнач - Новое значение. Возврат: 1 - установка успешно произведена, 0 - произошла ошибка при установке
ЗаменитьЭксзБазовогоКласса (стрИмяКласса>, <нЗначениеКласса) / ReplaceInstBaseClasses(strNameClass>, <uValOfClass) - Предназначен для замены экземпляра базового класса в уже созданной иерархии классов. Изменения базового класса отражаются только на объекте, для которого был вызван данный метод. - стрИмяКласса - имя базового класса, экземпляр которого мы собираемся заменять. - нЗначениеКласса - новый экземпляр базового класса. Возврат: 1- замена успешно произведена, 0 - произошла ошибка при замене
_ПриОткрытии / _OnOpen - данный метод устарел и оставлен для совместимости.
_ВыброситьИскл (ОбъектИскл) / _Throw(Object) - формирует исключение с объектом- исключением. Вызов этого метода в модуле КОП прекращает его выполнение и данное исключение передается дальше в другие модули для поиска обработчика исключения (раскрутка стека). Если такой обработчик не будет найден, то выполнение последнего модуля будет прекращено с выводом диагностического сообщения в окно сообщений 1С. Получить объект "Исключение" можно с помощью метода GetExeption()/ПолучитьИсключение () дополнительного класса ExecuteModule/ ВыполняемыйМодуль . ОбъектИскл - любой объект 1С.
_ПолучитьКод () / _GetCode() - функция, которая должна вернуть строковое представление объекта;
_SQLCreate (Value, obMDW) - процедура, которую должен реализовать КОП для типизации значения поля выборки объекта ODBCRecordset типом этого КОП (виртуальный конструктор). Должна быть объявлена в модуле с ключевым словом Экспорт. Должна иметь два или меньше параметров. Её вызывает объект ODBCRecordset при получении значения поля выборки, при типизации типом этого КОП. Параметры: - Value - значение поля выборки без типизации; - obMDW - статический объект типа MetaDataWork.
Файл определения классов должен быть расположен в одном каталоге с файлом конфигурации и называться Defcls.prm, если файла с таким именем не обнаружено, ВК осуществляет поиск в текущей конфигурации обработки с именем Defcls. Синтаксис языка определения классов следующий:
// - комментарий для программы класс (class) имя класса = имя файла класса реализации : <имя базового класса>, <имя базового класса> { Объявления методов класса };
Текст КОП возможно хранить в конфигурации в виде обработок, для этого необходимо указать имя обработки и прибавить окончание @MD, с соблюдением регистра букв у окончания.
Пример файла инициализации компоненты:
[alias_path] Псевдоним1 = С:\Классы1С_1\ Псевдоним2 = С:\Классы1С_2\
Далее в файле определения пути к классам необходимо написать следующее:
класс МойКласс = #Псевдоним1\ Псевдоним1.ert {};
Любой тип - может быть заменен ключевым словом Неопределенный (Undefine), что отключает для данного параметравозвращаемого значения проверку типов. Типом может быть Справочник, что означает возможность передавать в параметр метода справичник любойго вида справочника, (аналогичное поведение для типов Документ, и Регистр, и т.п.) - смысл, аналогичный типу "Справочник" или ГрупповойКонтекст, когда типом параметра может быть контекст любого модуля. (обязательный)
Примеры:
класс Базовый_1=Base_1.txt { void Метод1(Число пар1, Строка пар2); Число Метод2(Число пар1, Дата пар2); }; класс Производный_1=Derive_1.txt: Базовый_1, Справочник.Спр1, ТаблицаЗначений { void Метод1(Число пар1, Строка пар2); // Данный метод переопределяет метод //базового класса Число КоличествоСтрок (Число Колич); // В данном случае мы переопределили //метод базового класса ТаблицаЗначений и добавили проверку типов. В реализации этого //метода можно переадресовать вызов базовому классу void Метод3(ОС ОС); // в этот метод мы должны передаем передавать КОП "ОС" }; класс ОС=OC.txt : Справочник.ОсновныеСредства, Справочник.НематериальныеАктивы { void Метод1(Базовый_1 пар1); // В данном случае в пар1 можно передавать //объекты типа "Базовый_1" и его производный класс "Производный_1" void Метод2(Производный_1 пар1); // в отличие от предыдущего случае в пар1 мы //можем передавать объекты типа "Производный_1", но не "Базовый_1" Число ВыбратьЭлементы(Число чРежим); Число ПолучитьЭлемент(Число чРежим); };
Внимание: проверка типов может быть отключена по требованию пользователя, об этом см. ниже.
Для каждого КОП необходим отдельный файл реализации (как текстовый, так и отчет 1С), расположенный в каталоге БД или в папке, указанной в определении имени файла класса, либо в папке, указанной в псевдониме пути. С именем класса связывается файл реализации КОП в файле определения Defcls.prm.
Синтаксис языка файла реализации КОП полностью соответствует синтаксису языка 1С:Предприятия, плюс возможно использовать препроцессорные директивы.
Открытые атрибуты КОП определяются как общие переменные в модуле с ключевым словом Экспорт.
Закрытые атрибуты КОП определяются как общие переменные в модуле без ключевого слова Экспорт.
Обращение к методам компоненты из файла реализации КОП (модуля КОП), таких таким как: ПолучитьБазовыйКласс , осуществляется через Контекст данного модуля. Пример:
// В начале каждого модуля определяем закрытую функцию для получения // контекста. Функция GetThis(Конт) Возврат Конт; КонецФункции // Реализация метода "Метод1" класса "Производный_1" см. предыдущий пример Процедура Метод1 (пар1, пар2) Экспорт Конт = GetThis(Контекст); Базовый_1 = Конт.ПолучитьБазовыйКласс("Базовый_1"); Если Базовый_1 <> 0 Тогда КонецЕсли; КонецПроцедуры
Attention!
Предупреждение: нельзя сохранять контекст класса в его атрибуте, т.е. запрещен следующий алгоритм:
Перем Конт; Функция GetThis(Конт) Возврат Конт; КонецФункции ............... Конт = GetThis(Контекст); ...............
Если Вы будете использовать такое присваивание, объекты класса, созданные Вами в алгоритмах с помощью конструкции СоздатьОбъект("ИмяКлассаКОП"), никогда не уничтожатся (memory leaks), из-за циклической ссылки на объект внутри модуля реализации КОП. Это замечание также справедливо и для взаимных ссылок, когда один экземпляр класса содержит в себе ссылку на другой, и этот другой, в свою очередь, имеет ссылку на первый.
В каждой реализации класса можно создать процедуры Конструктор () англ. Constructor() и Деструктор () англ. Destructor(), которые вызываются, соответственно, в моменты создания экземпляра класса и его уничтожения (Конструктор() - объект создан, Деструктор() - объект уничтожен). Реализация данных процедур не обязательна.
Для контроля установки/записи атрибутов необходимо определить предопределенные методы (Процедура) в модуле реализации КОП (слово Экспорт к данным методам применять не обязательно):
При чтении атрибута класса вызывается метод ПриПолучении_ИмяАтрибута (ЗначениеАтрибута) англ. OnGet_, где ИмяАтрибута - имя атрибута, определенного в модуле КОП, из которого выполняют считывание, в переменную ЗначениеАтрибута нужно вернуть текущее значение атрибута. Данный метод не изменяет состояние самого атрибута по умолчанию.
При записи в атрибут класса вызывается метод ПриЗаписи_ИмяАтрибута (ЗначениеДляУст) англ. OnWrite_, где ИмяАтрибута - имя атрибута, определенного в модуле КОП, для которого устанавливают значение, передаваемое в параметре ЗначениеДляУст. Атрибут доступен в коде данного метода, и для его установки необходимо присвоить атрибуту полученный параметр метода. Данный метод не изменяет состояние самого атрибута по умолчанию.
Динамические атрибуты класса реализуются следующим образом: - Класс (или его клиент(не рекомендуется)) должен самостоятельно добавить динамическое свойство с помощью встроенного метода КОП ДобавитьДинамическоеСвойство (стрИмяНовогоСвойства)
в классе необходимо определить 2 предопределенных метода класса:
При чтении динамического атрибута класса вызывается
предопределенная функция _ПриЧтенииСвойства (стрИмяСвойства) англ. _OnReadProperty(strNameOfProperty) в параметр "стрИмяСвойства" передается название атрибута, как оно было указано в вызывающем коде. Возвращать данный метод обязан значение считанного атрибута с названием, полученным из параметра метода "стрИмяСвойства".
При установке значения атрибута экземпляра класса вызывается предопределенная процедура _ПриЗаписиСвойства (стрИмяСвойства, НовоеЗначениеАтриб), где "стрИмяСвойства" - имя записываемого свойства, а в параметре "НовоеЗначениеАтриб" содержится новое значение атрибута, т.е. правая часть выражения присваивания нового значения свойству класса.
В базовых классах иерархии возможно вызывать открытые методы производных классов (объявленные с ключевым словом "Экспорт"), воспользовавшись для этого контекстом модуля базового класса, также с помощью контекста возможно получить название конечного созданного класса (с помощью функции "СоздатьОбъект").
Экземпляры создаваемых классов можно сохранять в строку, а затем восстанавливать из неё (сериализация КОП). Для этого в классе необходимо определить следующие методы:
Если в классе реализован метод "КлассСохраняемый()", который возвращает значение, не равное 0, и реализован метод СохранитьКлассВСтроку(), то при использовании функции 1С ЗначениеВСтрокуВнутр(ЭкзКласса) вернет строку, сформированную в классе "ЭкзКласса" методом "СохранитьКлассВСтроку()".
Attention!
Для восстановления классов КОП, поддерживающих сериализацию, необходимо напрямую вызывать его метод "ЗагрузитьИзСтроки()", функция 1С "ЗначениеИзСтрокиВнутр" не может создавать экземпляры классов КОП. Поэтому можно реализовать вспомогательную функцию, которая бы создавала экз. классов КОП и вызывала их метод десериализации. Код может быть следующим:
Функция глКлассИзСтрокиВнутр(стрЗнач) Экспорт сз = СоздатьОбъект("СписокЗначений"); сз.ИзСтрокиСРазделителями(стрЗнач); Если сз.РазмерСписка() > 0 Тогда стрНазваниеКласса = сз.ПолучитьЗначение(1); Если ПустоеЗначение(стрНазваниеКласса)=1 Тогда Возврат -2; КонецЕсли; Попытка о = СоздатьОбъект(стрНазваниеКласса); бКлассСохр = 0; Попытка бКлассСохр = о.IsSerializable(); Исключение Попытка бКлассСохр = о.КлассСохраняемый(); Исключение Возврат -4; КонецПопытки; КонецПопытки; Если бКлассСохр = 0 Тогда Возврат -4; КонецЕсли; Попытка о.LoadFromString(сз.ВСтрокуСРазделителями()); Исключение Попытка о.ЗагрузитьИзСтроки(сз.ВСтрокуСРазделителями()); Исключение Возврат -5; КонецПопытки; КонецПопытки; Возврат о; Исключение Возврат -3; КонецПопытки; Иначе Возврат -1; КонецЕсли; КонецФункции //глКлассИзСтрокиВнутр
Note
Автоматическое групповое сохранение и восстановление экземпляров классов, возможно, выполнять с помощью класса "DynaValue" (см. глава 3.6., описание методов "ВыгрузитьВСтроку", "ЗагрузитьИзСтроки", "ВыгрузитьВФайл", "ЗагрузитьИзФайла")
Примеры: Иерархия классов выглядит так:
class База = base.ert { void Метод1(); // Этот метод мы не переопределяем, а вызываем здесь // Метод2 производного класса void Метод2(); }; class Производный = derive.ert : Тест14_База { void Метод2(); // переопределяем метод базового класса };
Модуль реализации класса "База":
Функция GetThis(Конт) Возврат Конт; КонецФункции Процедура Метод2() Экспорт Сообщить("База::Метод2"); КонецПроцедуры Процедура Метод1() Экспорт Сообщить("ТипзначенияСтр(Контекст) = "+ТипзначенияСтр(GetThis(Контекст))); // получаем название текущего класса GetThis(Контекст).Метод2(); // вызываем переопределенный метод КонецПроцедуры
Модуль реализации класса "Производный":
Процедура Метод2() Экспорт Сообщить("Производный::Метод2"); КонецПроцедуры
Модуль:
Сообщить("Создали Производный класс"); копПрозв = СоздатьОбъект("Производный"); копПрозв. Метод1(); Сообщить("Создали База класс"); копБаза = СоздатьОбъект("База"); копБаза. Метод1();
Вывод на экран будет следующим:
Создали Производный класс Производный Производный::Метод2 Создали База класс База База::Метод2
В одном модуле можно хранить код нескольких классов. Для этого необходимо код каждого класса заключить в следующие скобки:
//# ClassBegin <ClassName1>; Перем ПеременнаяКласса1; Процедура Конструктор() ......... //# ClassEnd <ClassName1>; //# ClassBegin <ClassName2>; Перем ПеременнаяКласса2; Процедура Конструктор() ......... //# ClassEnd <ClassName2>;
Где вместо <ClassName1> нужно указать имя класса (без угловых скобок). Ограничения:
Директивы препроцессора 1С++ могут быть в файлах объявления интерфейсов классов (Defcls.prm), в файлах реализации КОП и во всех модулях 1С, кроме модуля глобального модуля и модулей внешних обработок/отчетов. Символы препроцессора, объявленные в файле объявления класса, действительны в области видимости модулей реализации класса, т.е. доступны в файлах реализации КОП. Внимание: из-за раздельной интерпретации файла Defcls.prm и файлов реализации КОП изменения, определения и отключение символов препроцессора осуществляются независимо. Работа препроцессора гарантируется в следующей последовательности: сначала обрабатывается файл Defcls.prm, затем в неопределенной последовательности обрабатываются файлы реализации КОП.
Если символ определен, что равнозначно "истине", или символы, объединённые логическими операторами дают в результате "истину", то код, заключенный между директивами //#if и //#elif или //#else или //#endif, буден включен на выполнение. Symbol - это символ, который будет тестироваться на определенность ранее директивой //#define. Перед символом можно использовать знак ! (логическое отрицание). Operator:
= (равно); != (не равно); & (И); \| (ИЛИ);
тело с кодом, открытое директивой //#if, должно закрываться директивами //#elif , //#else, //#endif;
Для отладки модулей классов необходимо выполнить следующие действия:
Настройка компоненты осуществляется с помощью настройки параметров 1С, Сервис --> Параметры ... --> Закладка "Настройка 1C++". Данная закладка появляется только после загрузки компоненты методом 1С ЗагрузитьВнешнююКомпоненту.
На данной закладке присутствуют три флажка в виде кнопок: "Проверка типов" и "Оптимизация", "Отладка":