Содержание
ООП - объектно-ориентированное программирование, является на сегодняшний день одной из самых популярных технологий создания программных средств. Данная разработка направлена на устранение недостатка отсутствия ООП в языке 1С. Как известно, ООП базируется на трех основных принципах: полиморфизм, наследование и инкапсуляция; в данной разработке для возможной технической реализации принципов, были добавлены некоторые языковые средства. Также добавлена возможность строгой проверки типов для параметров методов классов, определяемых пользователем (КОП) и некоторые другие классы для расширения парадигмы ООП.
Наследование - это обобщение объектов за счет выведения общего поведения в логически связанных сущностях проекта. Возможность повторно использовать код и отделить интерфейс объекта от его реализации с целью повышения взаимозаменяемости и расширения частей системы без перепрограммирования и дополнительного тестирования множества модулей. Наследование позволяет представить на логическим уровне модель проектирования учетной системы более приближенно к проектируемой области. Наследование необходимо реализовать для достижения следующих целей:
В реализации наследования в ВК (внешней компоненте 1С:Предприятия) 1С++ необходимо определить следующий функционал:
Полиморфизм - заключается в переопределении поведения объекта с помощью специализации обобщенного класса, т.е. переопределение методов интерфейсов базового класса (общий класс) его наследником (более специализированным классом). Полиморфизм позволит:
Инкапсуляция - сокрытие деталей реализации классов за четко определенным интерфейсом. Это позволит разрабатывать компоненты и настраивать их взаимодействие с меньшими зависимостями между ними, что в свою очередь, уменьшит время тестирования и вероятность появления ошибок в алгоритмах программы.
Каждый класс, определенный пользователем (КОП), создается в 1С с помощью метода СоздатьОбъект(ИмяКласса).
Для каждого КОП определен стандартный интерфейс, состоящий из следующих методов:
ПолучитьБазовыйКласс (ИмяБазовогоКласса) / GetBaseClass(strNameOfBaseClass)
Описание: | возвращает объект базового класса для класса предка, имя которого передается в качестве строки в параметр метода "ИмяБазовогоКласса". Если объекта базового класса не существует, то метод возвратит 0. Данный метод предназначен для вызова переопределенных методов базовых классов из методов классов наследников и получения/установки атрибутов базовых классов. |
---|---|
Пример: | имеются следующие справочники: "Необоротные активы", в котором содержатся общие реквизиты всех необоротных активов предприятия, и справочники - спецификаторы, такие как "Основные средства", "Нематериальные активы", которые, в свою очередь, добавляют специфические реквизиты сущностей к общим реквизитам необоротных активов. Исходя из описанной выше идеологии, мы строим следующую иерархию классов: КОП с рабочим названием "ОС" наследует от классов 1С: справочника "Необоротные активы" и "Основные средства" и переопределяет методы "ВыбратьЭлементы()" и "ПолучитьЭлемент()", т.к у КОП "ОС" базовые классы имеют такие же методы, программист обязан разрешить неоднозначность вызова, воспользовавшись в реализации методов "ВыбратьЭлементы()" и "ПолучитьЭлемент()" класса "ОС" методом ПолучитьБазовыйКласс("Основные средства") для вызова его функций выборки и итераций и ПолучитьБазовыйКласс ("Необоротные активы") для проведения аналогичных операций. |
ОтправитьСообщениеМодулюХоз (КтоОтправил, ВидСообщения, Данные) / SendMessageOwnMod(WhoSend, KindMessage, Data).
Описание: | Вызывает предопределенную функцию ОбработкаСобытияОтКласса(отКого, стрСобытие, Данные), реализованную в модуле, вызывающем работающий в данный момент метод КОП, возвращает любое значение, которое будет получено после вызова ОтправитьСообщениеМодулюХоз() в модуле КОП. |
---|
ПолучитьПуть () / GetPathName().
Описание: | Возвращает полный путь и название файла, в котором хранится модуль реализации класса. |
---|
ПолучитьКонтекстОкружения () / GetEnvContext()
Возвращаемое значение: | |
---|---|
Возвращает контекст, из которого получил управление модуль КОП |
|
Описание: | Получить контекст окружения из модуля КОП. Метод считается устаревшим и оставлен только для совместимости. |
Дополнение: | Проблема метода ПолучитьКонтекстОкружения() в том, что класс получает контекст формы, что-то с ним делает, а форма об этом ни сном, ни духом. Причем ситуация усугубляется тем, что ПолучитьКонтекстОкружения() можно вызвать в каком-нибудь из базовых классов. Или в классе может быть вызван метод другого класса, который и вызывает ПолучитьКонтекстОкружения(). Эту цепочку можно удлиннять и комбинировать. Таким образом, мы получаем ситуацию, когда теоретически любая форма может быть изменена в любом классе. И найти, кто же и что нам поменял, можно только глобальным контекстным поиском. Еще прочтите http://itland.ru/forum/index.php?showtopic=12465&st=0&p=65557&#entry65557 |
ПолучитьСписокПараметров (стрИмяМетода) / GetParamsList(strNameOfMeth)
Возвращаемое значение: | |
---|---|
СписокЗначений с параметрами метода. | |
Описание: | Получить список со значениями неявных параметров, переданных в метод, название которого необходимо передать в качестве параметра. Данный метод можно использовать только в теле метода класса, который был определен с последним формальным параметром ":" в файле определения КОП. Более подробное описание см. в пункте 1.3., раздел "Неопределенное количество параметров". |
УстановитьПараметрПоИндексу (стрИмяМетода>,<чИнд>,<нЗнач) / SetOnIndexParams(strNameOfMeth >,<nInd>,<uVal)
Описание: | Метод предназначен для получения ссылки значения неявного параметра по его порядковому номеру и установке нового значения по данной ссылке. |
---|---|
Параметры: |
|
Возврат: | 1 - установка успешно произведена, 0 - произошла ошибка при установке |
ЗаменитьБазовыйОбъект (стрИмяКласса>, <нЗначениеКласса) / ReplaсeBaseObject(strNameClass>, <uValOfClass)
Описание: | Предназначен для замены экземпляра базового класса в уже созданной иерархии классов. Изменения базового класса отражаются только на объекте, для которого был вызван данный метод. |
---|---|
Параметры: |
|
Возврат: | 1- замена успешно произведена, 0 - произошла ошибка при замене |
_ПриОткрытии / _OnOpen - данный метод устарел и оставлен для совместимости.
_ВыброситьИскл (ОбъектИскл) / _Throw(Object)
Описание: | формирует исключение с объектом- исключением. Вызов этого метода в модуле КОП прекращает его выполнение и данное исключение передается дальше в другие модули для поиска обработчика исключения (раскрутка стека). Если такой обработчик не будет найден, то выполнение последнего модуля будет прекращено с выводом диагностического сообщения в окно сообщений 1С. Получить объект "Исключение" можно с помощью метода ПолучитьИсключение () дополнительного класса ВыполняемыйМодуль . |
---|---|
Параметры: | ОбъектИскл - любой объект 1С. |
_ПолучитьКод () / _GetCode()
Описание: | функция, которая должна вернуть строковое представление объекта; |
---|
_SQLCreate (Value, obMDW)
Описание: | процедура, которую должен реализовать КОП для типизации значения поля выборки объекта ODBCRecordset типом этого КОП (виртуальный конструктор). Должна быть объявлена в модуле с ключевым словом Экспорт. Должна иметь два или меньше параметров. Её вызывает объект ODBCRecordset при получении значения поля выборки, при типизации типом этого КОП. |
---|---|
Параметры: |
|
ЯвляетсяОбъектом (ИмяПроверяемогоТипа) / _IsObject(ИмяПроверяемогоТипа)
Параметры: |
|
---|---|
Описание: | Функция, которая проверяет, является ли данный объект класса указанным типом или производным от этого типа. |
Для включения подобной возможности необходимо использовать специальную переменную препроцессора _NOW_PREPARE_CLASS
Примерный код подобного класса выглядит след.образом
//#if _NOW_PREPARE_CLASS // -- данные и методы класса //#endif //_NOW_PREPARE_CLASS //#if !_NOW_PREPARE_CLASS // -- данные и процедуры формы Процедура ПриОткрытии() // код открытия формы КонецПроцедуры //#endif //! _NOW_PREPARE_CLASS
Файл определения классов должен быть расположен в одном каталоге с файлом конфигурации и называться 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С:Предприятия, плюс возможно использовать препроцессорные директивы.
Открытые атрибуты КОП определяются как общие переменные в модуле с ключевым словом Экспорт.
Закрытые атрибуты КОП определяются как общие переменные в модуле без ключевого слова Экспорт.
Обращение к методам компоненты из файла реализации КОП (модуля КОП), таких таким как: `ПолучитьБазовыйКласс`_ , или обращение к виртуальным (экспортным) методам и атрибутам осуществляется через метод вирт() . Пример:
// Реализация метода "Метод1" класса "Производный_1" см. предыдущий пример Процедура Метод1 (пар1, пар2) Экспорт Базовый_1 = вирт().ПолучитьБазовыйКласс("Базовый_1"); Если Базовый_1 <> 0 Тогда КонецЕсли; КонецПроцедуры Процедура Метод2 () Экспорт вирт().Метод1(1, 2); КонецПроцедуры
Также есть возможность обращения к виртуальным (экспортным) методам и атрибутам класса без использования виртуальности с помощью метода Я() Например, см. ДинамическиеАтрибуты
Примеры использования вирт() и я() см. по адресам http://www.1cpp.ru/forumfiles/Attachments/1cpp_test_virt_2.rar
или в конфигурации юнит-тестов 1С++ - класс ООПТесты::Тест_Вирт...
Также подробная инфа о вирт() и я() есть в ветке на форуме 1С++ http://www.1cpp.ru/forum/YaBB.pl?num=1207661901/0
Старый вариант, оставленный для совместимости: Обращение к методам компоненты из файла реализации КОП (модуля КОП), таких таким как: ПолучитьБазовыйКласс , осуществляется через Контекст данного модуля. Пример:
// В начале каждого модуля приходится определять закрытую функцию для получения // контекста. Поэтому лучше пользоваться вирт() ! Функция Этот(Конт) Возврат Конт; КонецФункции // Реализация метода "Метод1" класса "Производный_1" см. предыдущий пример Процедура Метод1 (пар1, пар2) Экспорт Конт = Этот(Контекст); Базовый_1 = Конт.ПолучитьБазовыйКласс("Базовый_1"); Если Базовый_1 <> 0 Тогда КонецЕсли; КонецПроцедуры
Attention!
Предупреждение: нельзя сохранять контекст класса в его атрибуте, т.е. запрещен следующий алгоритм:
Перем Конт; ............... Конт = вирт(); ...............
Attention!
или:
Перем Конт; Функция GetThis(Конт) Возврат Конт; КонецФункции ............... Конт = GetThis(Контекст); ...............
Если Вы будете использовать такое присваивание, объекты класса, созданные Вами в алгоритмах с помощью конструкции СоздатьОбъект("ИмяКлассаКОП"), никогда не уничтожатся (memory leaks), из-за циклической ссылки на объект внутри модуля реализации КОП. Это замечание также справедливо и для взаимных ссылок, когда один экземпляр класса содержит в себе ссылку на другой, и этот другой, в свою очередь, имеет ссылку на первый.
В каждой реализации класса можно создать процедуры Конструктор () англ. Constructor() и Деструктор () англ. Destructor(), которые вызываются, соответственно, в моменты создания экземпляра класса и его уничтожения (Конструктор() - объект создан, Деструктор() - объект уничтожен). Реализация данных процедур не обязательна.
В базовых классах иерархии возможно вызывать открытые методы производных классов (объявленные с ключевым словом "Экспорт"), воспользовавшись для этого контекстом модуля базового класса ( вирт().Метод() ), также с помощью контекста возможно получить название конечного созданного класса ( ТипЗначенияСтр(вирт() ).
Для контроля установки/записи атрибутов необходимо определить предопределенные методы (Процедура) в модуле реализации КОП (слово Экспорт к данным методам применять не обязательно):
Динамические атрибуты класса реализуются следующим образом:
Внимание: вызов метода ДобавитьДинамическоеСвойство необходимо производить не через Сам(Контекст) или вирт(), а с помощью спец.метода КОП Я - Я().ДобавитьДинамическоеСвойство. Иначе невозможно будет обратиться к методу базового класса, вызывающего данный код из метода класса-наследника.
в классе необходимо определить 2 предопределенных метода класса:
При чтении динамического атрибута класса вызывается
предопределенная функция _ПриЧтенииСвойства (стрИмяСвойства) англ. _OnReadProperty(strNameOfProperty) в параметр "стрИмяСвойства" передается название атрибута, как оно было указано в вызывающем коде. Возвращать данный метод обязан значение считанного атрибута с названием, полученным из параметра метода "стрИмяСвойства".
При установке значения атрибута экземпляра класса вызывается предопределенная процедура _ПриЗаписиСвойства (стрИмяСвойства, НовоеЗначениеАтриб), где "стрИмяСвойства" - имя записываемого свойства, а в параметре "НовоеЗначениеАтриб" содержится новое значение атрибута, т.е. правая часть выражения присваивания нового значения свойству класса.
- Пример использования ВеткаПоДинамическимАтрибутам
Экземпляры создаваемых классов можно сохранять в строку, а затем восстанавливать из неё (сериализация КОП). Для этого в классе необходимо определить следующие методы:
Если в классе реализован метод "КлассСохраняемый()", который возвращает значение, не равное 0, и реализован метод СохранитьКлассВСтроку(), то при использовании функции 1С ЗначениеВСтрокуВнутр(ЭкзКласса) вернет строку, сформированную в классе методом "СохранитьКлассВСтроку()".
Для восстановления классов КОП, поддерживающих сериализацию, необходимо, как обычно в 1С, использовать встроенную функцию 1С "ЗначениеИзСтрокиВнутр" Например, СериализованноеПредставление = ЗначениеИзСтрокиВнутр(Объект); Объект = 0; Объект = ЗначениеИзСтрокиВнутр(СериализованноеПредставление);
Для реализации универсального интерфейса загрузки КОП должен определить 5 методов Число СобытиеЗагрузки_НачалоЗагрузки(КолвоКолонок, КолвоСтрок) Число СобытиеЗагрузки_ДобавитьКолонку(ИмяКолонки, НомерТипа) Число СобытиеЗагрузки_ДобавитьДанные(Вектор) Число СобытиеЗагрузки_ОкончаниеЗагрузки() Строка СобытиеЗагрузки_СообщениеОбОшибке()
Необходимо реализовать все 5 методов, иначе будет выдана ошибка. После реализации подобных методов для объектов данного класса можно будет выполнить универсальную выгрузку из различных контейнеров, например, ИТЗ, Вектор/АссоциативныйВектор и др.коллекции - ИТЗ.Выгрузить(ОбъектКОП).
Более подробно см. ветку http://www.1cpp.ru/forum/YaBB.pl?num=1216110955/0
Примеры: Иерархия классов выглядит так:
class База = base.ert { void Метод1(); // Этот метод мы не переопределяем, а вызываем здесь // Метод2 производного класса void Метод2(); }; class Производный = derive.ert : Тест14_База { void Метод2(); // переопределяем метод базового класса };
Модуль реализации класса "База":
Процедура Метод2() Экспорт Сообщить("База::Метод2"); КонецПроцедуры Процедура Метод1() Экспорт Сообщить("ТипзначенияСтр(Контекст) = "+ТипзначенияСтр( вирт() )); // получаем название текущего класса вирт().Метод2(); // вызываем переопределенный метод КонецПроцедуры
Модуль реализации класса "Производный":
Процедура Метод2() Экспорт Сообщить("Производный::Метод2"); КонецПроцедуры
Модуль:
Сообщить("Создали Производный класс"); копПрозв = СоздатьОбъект("Производный"); копПрозв. Метод1(); Сообщить("Создали База класс"); копБаза = СоздатьОбъект("База"); копБаза. Метод1();
Вывод на экран будет следующим:
Создали Производный класс Производный Производный::Метод2 Создали База класс База База::Метод2
В одном модуле можно хранить код нескольких классов. Для этого необходимо код каждого класса заключить в следующие скобки:
//# ClassBegin <ClassName1>; Перем ПеременнаяКласса1; Процедура Конструктор() ......... //# ClassEnd <ClassName1>; //# ClassBegin <ClassName2>; Перем ПеременнаяКласса2; Процедура Конструктор() ......... //# ClassEnd <ClassName2>;
Где вместо <ClassName1> нужно указать имя класса (без угловых скобок). Наличие пробела после # и ClassBegin обязательно. Также обязательно наличие ; сразу после имени класса. Ограничения:
Директивы препроцессора 1С++ могут быть в файлах объявления интерфейсов классов (Defcls.prm), в файлах реализации КОП и во всех модулях 1С, кроме модуля глобального модуля и модулей внешних обработок/отчетов. Символы препроцессора, объявленные в файле объявления класса, действительны в области видимости модулей реализации класса, т.е. доступны в файлах реализации КОП. Внимание: из-за раздельной интерпретации файла Defcls.prm и файлов реализации КОП изменения, определения и отключение символов препроцессора осуществляются независимо. Работа препроцессора гарантируется в следующей последовательности: сначала обрабатывается файл Defcls.prm, затем в неопределенной последовательности обрабатываются файлы реализации КОП.
Если символ определен, что равнозначно "истине", или символы, объединённые логическими операторами дают в результате "истину", то код, заключенный между директивами //#if и //#elif или //#else или //#endif, буден включен на выполнение. Symbol - это символ, который будет тестироваться на определенность ранее директивой //#define. Перед символом можно использовать знак ! (логическое отрицание). Operator:
= (равно); != (не равно); & (И); \| (ИЛИ);
тело с кодом, открытое директивой //#if, должно закрываться директивами //#elif , //#else, //#endif;
Для отладки модулей классов необходимо выполнить следующие действия:
ВНИМАНИЕ: Рекомендуется в клиентском режиме 1С:Предприятия выключать режим "Отладки".
Например, если в глобальном модуле написано Перем глОбъект; Процедура ПриНачалеРаботыСистемы() ЗагрузитьВнешнююКомпоненту("1cpp.dll"); // а теперь включить режим отладки глОбъект = СоздатьОбъект("КОП"); // объект создан в режиме отладки, его отладочная форма видна на экране КонецПроцедуры // ПриНачалеРаботыСистемы После запуска такой конфигурации в режиме отладки 1С невозможно закрыть стандартными средствами :( К сожалению, не помогает и явный сброс объекта Процедура ПриЗавершенииРаботыСистемы() глОбъект = 0; КонецПроцедуры // ПриЗавершенииРаботыСистемы До этой процедуры дело просто не доходит :( Простой вариант решения в этом случае - спец. обработка, обнуляющая все глобальные объекты
Настройка компоненты осуществляется с помощью настройки параметров 1С, Сервис --> Параметры ... --> Закладка "Настройка 1C++". Данная закладка появляется только после загрузки компоненты методом 1С ЗагрузитьВнешнююКомпоненту.
На данной закладке присутствуют три флажка в виде кнопок: "Проверка типов" и "Оптимизация", "Отладка":