Компонента представленная в этой статье привносит в 1С возможность определять сущности вида:
Класс Машинка
Функция ДайКоличествоКолес(), Абстрактная
КонецКласса
Класс МашинкаЛегковая Наследует Машинка
Функция ДайКоличествоКолес(), Замещает
КонецКласса
Класс МашинкаБТР87
Внутр:
Перем м_колКолес;
Экспорт:
Функция ДайКоличествоКолес(), Замещает
КонецКласса
И в обычном модуле 1С (привязка реализации методов):
Функция МашинкаЛегковая_ДайКоличествоКолес(Знач ЭтотКласс) Экспорт
Возврат 4;
КонецФункции
Функция МашинкаБТР87_ДайКоличествоКолес(Знач ЭтотКласс) Экспорт
Возврат ЭтотКласс.м_колКолес;
КонецФункции
Далее возможно эти сущности создавать и оперировать ими как обычными объектными значениями 1С.
Т.е. обычные и привычные в других языках программирования классы. Синтаксис выбран соответствующий духу Бейсика и более всего похож на vb.net от Microsoft. Реализация также каноническая и претендующая на соответствие возможностям vb.net, c# или java.
Но это пока черновик (или версия-0) только ограниченно демонстрирующая эту технологию. Пример в демонстрационной конфигурации в конце этой статьи выбран не удачно. А некоторые недоработки и неприспособленность редактора текста модуля 1С к этой функциональности превращают код в чудовищную кашу. Поэтому из самой статьи этот код я убрал. По свидетельству немногочисленных выживших после увиденного «ЭТО вызывает быстротечный ООП головного мозга с неминуемым взрывом последнего». Ситуация изучается.
На словах. Код в конфигурации является некоторой переработкой шуточной программы <Hello World Enterprise Edition> на java. Т.е. он демонстрирует не просто ООП, а наихудшее его enterprise проявление. Поэтому даже название модуля сокращено до двусмысленности – ОопХелл. Собственно если это и демонстрационная программ, то точно не для первой статьи. На этом примере автор отлаживал и хотел продемонстрировать возможности компоненты в использования ООП-техник известных как <паттерны проектирования>. Также можно почитать у «заклятых друзей» - <Паттерны проектирования в ABAP примерах>. В общем – выглядит это пока чудовищно, но работает.
Тем кто просто пока интересуется «Что за зверь такой ООП?» я бы не рекомендовал скачивать и разбираться в версии-0. Даже из любопытства не всем нужно смотреть как делается колбаса. Можно ведь и заработать стойкое отвращение к этому вполне полезному и обыденному продукту. Более правильным будет почитать литературу и поиграться с vb.net или java. Если эта технология получит развитие, то здесь будет тоже самое или очень близкое.
Также должен предупредить ищущих быстрой практической пользы. Возможности компоненты пока ограничены и объекты компоненты с объектами платформы не взаимодействуют. Пока можно только поиграться в ООП. И то с риском для здоровья, как выяснилось. Просто следите за развитием технологии. В конце концов, делаю я это не только исключительно из тяги к прекрасному. В конце пути обязательно должен быть профит, просто пока, издалека, его не совсем отчетливо видно.
Отдельно ожидающим простоты и легкости кода типовых конфигураций. Разработчики проделали гигантскую работу убрав все сложности под капот платформы. И убрали они туда в том числе и ООП. ООП это инструмент для решения больших и сложных проблем. Простым он не будет никогда. Не бывает простых решений сложных проблем. В пределах возможностей читабельность кода повышать можно, но концептуальная легкость и простота с ООП не совместима.
Немного относительно самого ООП, хотя это не дискуссионная статья об ООП. Статью об ООП я только планирую. Проблематика ООП применительно к 1С корнями будет восходить к вопросам «Что есть решения от 1С или аналогичные продукты: это системы учета или управленческие инструменты?» и приземленней к 1С «Это программа (пул программ) или среда\платформа их разработки». Эти вопросы сами по себе масштабны и дискуссионны. Они не имеют единого ответа. Отвечая на них, человек скорее выбирает его и только его мировоззренческую позицию и эта позиция во многом определит его отношение к ООП. А сам по себе ООП это просто инструмент. Со своими преимуществами и недостатками. Инструмент для профессионалов.
Итак. Всех кто еще не испугался, не заснул и не переключился на что-нибудь полезное прошу за мной дальше.
ОопХелл.Подключить();
ОопХелл.Здрасьте();
Кусочек ужаса из демки:
Функция _Класс_ФабрикаАгентов_()
Возврат ""
"Класс ФабрикаАгентов"
"ВНУТР:"
" Конструктор() := ФабрикаАгентов_Конструктор"
"ВНУТР:"
" Перем м_фабрика, СТАТИК"
" Перем м_стдАгент"
"ЭКСПОРТ:"
" Свойство Фабрика, СТАТИК ЧТЕНИЕ := ФабрикаАгентов_Объект"
"ЭКСПОРТ:"
" Свойство СтандартныйАгент ЧТЕНИЕ := ФабрикаАгентов_СтандартныйАгент"
" Функция СделайАгента(имя_ Как Строка) ПЕРЕГРУЗКА := ФабрикаАгентов_СделайАгентаС"
" Функция СделайАгента(имя_ Как Строка, язык_ Как Строка) ПЕРЕГРУЗКА := ФабрикаАгентов_СделайАгентаСС"
"КонецКласса"
КонецФункции
...
Функция Агент_Поздоровайся(знач ЭтотКласс, другой_) Экспорт
Перем рез;
Перем другой_агент;
Перем другой_агент_неверб;
Попытка
другой_агент = другой_.ДайИнтерфейс("Агент");
Исключение
КонецПопытки;
Если другой_агент = Неопределено Тогда
Возврат СС.РезультатыМенеджер.Результат_Не_неАгент;
КонецЕсли;
Если другой_агент = ЭтотКласс.ДайИнтерфейс("Агент") тогда
Возврат СС.РезультатыМенеджер.Результат_Ок_сСобой;
КонецЕсли;
рез = другой_агент.ПримиСообщение(ЭтотКласс.МоеЗдрасьте);
Если не рез.Успех тогда
если рез = СС.РезультатыМенеджер.Результат_не_неПонял и ЭтотКласс.м_язык_другой <> Неопределено Тогда
рез = другой_агент.ПримиСообщение(СС.ФабрикаЗдрасьте.СделайЗдрасьте(ЭтотКласс.м_язык_другой));
Если рез = СС.РезультатыМенеджер.Результат_ок Тогда
рез = СС.РезультатыМенеджер.Результат_ок_сПереводом;
КонецЕсли;
КонецЕсли;
КонецЕсли;
Если не рез.Успех тогда
другой_агент_неверб = другой_агент.ДайИнтерфейс("НевербальнаяКоммуникация");
если другой_агент_неверб <> Неопределено тогда
рез = другой_агент_неверб.ПримиСообщение(СС.ФабрикаЗдрасьте.СделайЗдрасьтеЖест());
Если рез = СС.РезультатыМенеджер.Результат_ок Тогда
рез = СС.РезультатыМенеджер.Результат_Ок_Невербально;
КонецЕсли;
КонецЕсли;
КонецЕсли;
Возврат рез;
КонецФункции
По сути. Это пререлиз, черновик или версия 0. Т.е. сыровато, но сама технология, на мой взгляд, уже заслуживает публикации. Целью этой статьи является окончательно определиться с синтаксисом, поговорить, очертить границы и наметить план доработок. Особенно меня волнует синтаксис – его переделки наиболее болезненны. Также, несомненно, будут ошибки, и я буду очень благодарен, если мне на них укажут.
Техническое
Сразу. Версия-0: Количество аргументов методов ограничено 16. Значения по умолчанию для аргументов методов не поддерживаются. Объекты компоненты не поддерживают сериализацию. Коллекции 1С не поддерживаются. Обработка ошибок в зачаточном состоянии и малоинформативна, ассерты выведены в релиз. Течи памяти не проверял. Это черновик.
Компонента откомпилирована VS2015 для win32 (v140_xp, статическая линковка). Работаю я на win7-32 и тестирую (на данный момент) в 1С:Предприятие 8.3.6.2363. Все остальное в этой версии не пробовал. Ранее, в процессе экспериментов, были VS2010, Win2kSrv32 и платформы начиная с 8.3.4.365. Буду признателен, если кто подскажет типовые конфигурации которые стоит тестировать, а то все и везде это слишком много.
Все реализовано поверх родных интерфейсов 1С без какого-либо вмешательства в исходный код платформы. Связность с системой минимальна. Пул памяти 1С не используется. В системе объектов платформы не регистрируюсь, создать объекты компоненты можно только методами компоненты (Новый … - нельзя). Версия-0: своих ТИПов в систему также не добавляю, а мимикрирую под ФиксированнаяСтруктура. Моим типам платформа пока в отладочном табло свойства не разворачивает и это неудобно.
Стоимость экземпляра простейшего класса sizeof(long) + 3*sizeof(uintptr_t). Стоимость определителя класса не считал, но тяжелый. Стоимость каждого унаследованного или имплементированного интерфейса 3*sizeof(uintptr_t). Возможно доработать до 2*sizeof(uintptr_t) для простых классов. Стоимость вызова 1-2 virtual call. Переаллокация аргументов производится на стеке. Оценка быстродействия есть в демонстрационной конфигурации и здесь обсуждать ее считаю нецелесообразным. Затраты есть. Есть чуть резервов. На данном этапе и для данного типа приложения неважно.
Все объекты компоненты (и 1С) являются СОМ-объектам и время их жизни определяется их счетчиком ссылок. Соответственно все (за исключением синглетонов) подвержены <проблеме циклических ссылок>. Компонента никоим образом не контролирует это. Экземпляр класса уничтожается тогда, когда освобождены все ссылки на все его объекты-интерфейсы. Или зависает в памяти до завершения приложения, если прямо или опосредовано в его переменной сохранили его же самого. Определители классов – не синглетоны (версия 0). Объекты созданные компонентой нельзя сохранять в переменных модуля приложения без очистки этих переменных ПриЗавершенииРаботыСистемы(), или еще как. 1С выгружает компоненту до очистки переменных и попытка очистки переменных содержащих объекты созданные компонентой приводит к краху. По возможности нигде и никогда не используйте глобальные переменные.
Классы задаются текстовыми строками. Можно провести аналогию с реализацией SQL в 1С, которая тоже не является частью языка и в текстовом виде передается некоему механизму. Только SQL манипулирует данными, а escls манипулирует кодом. Механизм reflection в версии-0 не предусмотрен.
Несколько общих моментов
Делая это, я держал в голове некоторую концепцию «лингвистически корректного и академически правильного национального Бейсика». Поэтому «КлассДитя» НАСЛЕДУЕТ «КлассПапа». Наследует, а не расширяет, дополняет, углубляет, уточняет или еще как. Также можно писать «Свойство ... ЗАМЕЩАЕМОЕ», а «Метод ... ЗАМЕЩАЕМЫЙ» (версия-0: можно и наоборот – я все-таки не школьное сочинение проверяю).
Также считаю правильным, что класс определяется, а не описывается, декларируется или еще как. Название виртуальных методов замещаемыми тоже считаю правильным, поскольку без отсылок к истории и техническим деталям объяснить неподготовленному человеку в чем виртуальность не представляется возможным.
В моем понимании Бейсик – язык в первую очередь дружелюбный, а многословность его это обратная сторона дружелюбности и, иногда, необходимое зло. Сравните: «Асс Фуу(Баз Бар)»; и «Функция Фуу(Бар Типа Баз) Возвращает Асс». Обе конструкции концептуально, с учетом особенностей человеческого мышления, верны. Только они подходят к решению задачи с разных сторон.
Дружелюбность языка не означает его неполноценность. За критерий полноценности языка выбрана возможность выражения с его использованием общепринятой в программной индустрии концепции <паттернов программирования>.
Учет принципа разделения разработки. В иерархии наследования у каждого класса по умолчанию разные разработчики. Задача языка помочь каждому разработчику «дать» то что он хочет дать, и «не давать» ничего явно не указанного. В частности поэтому при трехуровневом наследовании конструкция «ЭтотКласс.БазовыйКласс.БазовыйКласс...» работать не будет. Разработчик «среднего» класса должен явно дать доступ своему наследнику к некоторой функциональности своего базового класса.
ООП – это зло, но ООМ (объектно-ориентированное мышление) - это добро. Добро невозможно без зла и единственный путь здесь – это сделать зло дружелюбным.
Документация
Объект класс – это совокупность объектов-интерфейсов класса. Объект-интерфейс класса создается компонентой и распознаются платформой как допустимое объектное значение. Его можно сохранять в переменные, передавать параметром, и т.д. В случае, если класс имеет секцию внутренних данных и\или методов, то компонентой создается два объекта-интерфейса класса. Один публичный – его возвращает метод .СоздатьКласс(), а другой приватный – его получают в качестве первого параметра методы реализации. Имя класса это также и имя публичного объекта-интерфейса класса. Если класс наследует другой класс, то у него будет уже четыре объекта-интерфейса – публичный\внутренний базового плюс публичный\внутренний самого класса. Если у базового класса есть защищенные элементы (не доступные публично, но доступные наследнику), то у него создается еще один внутренний объект-интерфейс возвращаемый свойством .МойБазовый внутреннего объекта-интерфейса класса наследника. Если класс реализует (имплементирует) интерфейс, то у него будет создан еще один именованный объект-интерфейс. И так далее. Все объекты-интерфейсы класса дают доступ строго только к определенным для них элементам класса. Объект-интерфейс базового класса (.МойБазовый ) содержит варианты элементов до переопределения их наследником. Все объекты-интерфейсы класса имеют функцию .ДайИнтерфейс(ИмяИнтерфейса), которая возвращает соответствующий объект-интерфейс или неопределенно. Любой внутренний интерфейс получить способом .ДайИнтерфейс(…) нельзя. Все свойства и методы объектов-интерфейсов реализуют свою функциональность путем вызова методов объектов платформы заданных при определении класса.
Собственно ИНТЕРФЕЙС это специальная форма класса у которого не может быть переменных, все свойства и методы (процедуры и функции) замещаемые и без указания реализации - АБСТРАКТНЫЕ. Т.е. голая декларация метаданных некоторого контекста смысл обретающая тогда, когда класс этот ИНТЕРФЕЙС реализует.
В дальнейшем, для обозначения объекта-интерфейса класса я буду использовать принятый в 1С термин «контекст» для исключения путаницы с принятом в программировании термином ИНТЕРФЕЙС.
Сам объект компоненты содержит методы:
Функция ОпределитьКласс(ТекстОпределения, ОбъектРеализации) - создает, внутренне регистрирует и возвращает объект ОпределительКласса. В ТекстеОпределения может быть более одного определения класса и\или интерфейса. Возвращается внутренний интерфейс объекта ОпределительКласса последнего определения.
Функция СоздатьДелегат(Объект, ИмяМетода) – создает объект Делегат. Этот объект имеет только один не именованный (версия-0) метод, при вызове которого, он вызывает заданный при его создании метод заданного объекта. Делегирует вызов. Тип метода (процедура или функция), а также аргументы определяются автоматически. Не именованный означает что вызвать его можно с любым именем - <делегат>.Вызвать(…) /.Выпонить(…) /.Провести(…) /.ДелайЧтоДолженБудьЧтоБудет(…) и т.д. Делегатов к свойствам и делегатов с привязкой аргументов в версии-0 нет.
Свойство СоздатьКласс – возвращает объект с динамически, по мере определения классов с публичными конструкторами, расширяемым набором методов с именами определяемых классов. Сигнатуры методов соответствуют сигнатурам определенных для класса конструкторов. Для использования в семантике <имя-переменной-компоненты>.СоздатьКласс.<имя-класса>(<аргументы-конструктора>).
Также, по мере определения классов, к объекту компоненты динамически добавляется свойства с именами классов возвращающие публичные интерфейсы объектов-определителей классов (объектов-определителей интерфейсов).
Свойство ПустаяСсылка – возвращает предопределенный в компоненте объект ПустаяСсылка (см. Деструктор)
Функция ДайТики() - версия-0, в тестовых целях возвращает GetTickCount()
Класс определяется методом компоненты .ОпределитьКласс(ТекстОпределения, ОбъектРеализации). Определение класса задается в текстовом виде через параметр ТекстОпределения. Через параметр ОбъектРеализации передается любой допустимый в 1С объект имеющий методы указанные в ТекстеОпределения класса в качестве привязки к реализации элементов класса. Первым параметром метода реализации должен быть параметр по-значению, в который компонента передает внутренний контекст реализуемого объекта.
Элементы класса – переменная, свойство, функция, процедура. Свойства и переменные не отличаются по способу обращения к ним через интерфейс класса – синтаксис свойства 1С. Но сами значения в случае переменных хранятся в самом экземпляре класса (или экземпляре определителя класса), а в случае свойств происходит обращение к методам платформы указанным в привязках реализации. Типы данных переменных – любые допустимые в 1С (а по сути любые COM-объекты). При создании все переменные принудительно получают значение 1С::неопределенно, а при уничтожении объекта принудительно очищаются (.Release()).
Общие элементы описания синтаксиса
В квадратных скобках [] опциональные параметры. Языковые конструкции в угловых скобках <>. Варианты в виде (<вариант-1> | <вариант-2>). БОЛЬШИМИ буквами термины. Термин это последовательность символов распознаваемая парсером как единое целое. На данный момент термины двуязычны ru\en. Термин КЛАСС означает строку Класс или Class. Регистр символов значения не имеет. Термины могут быть многовариантными – н.р. КАК предваряющий тип параметра или переменной представляется строками As, Как, Есть, Это, Типа. Еще раз – это черновик - возможно что-то лишнее, а чего-то не хватает
Точка с запятой в конце строки определения элемента опциональна. Переход на новую строку игнорируется.
Имена элементов (далее <имя>)
<имя> - это имя элемента в соответствии с соглашением о именовании 1С. Первый символ или буква или ‘_’ далее буквы и цифры в любой последовательности. Регистр символов игнорируется.
Многоязычность в версии-0 не поддерживается. Только грамматика в части терминов двуязычна (ru\en). Многоязычность имен впоследствии предполагается реализовывать с помощью специальных атрибутов. Например: [&EN = ClassFoo] КЛАСС КлассФуу.
На даный момент запрещается определять свойства (и переменные) с именами совпадающими с именами методов и наоборот. см.<задумано-но-не-сделано>::<элементы-по-умолчанию>
Запрещается определять свойства и методы с именами совпадающими с именами предопределенных элементов – н.р. СоздатьКласс, МойБазовый, Конструктор и т.д.
PS: Далее по тексту может встречаться <идентификатор>. В исходном BNF это <имя>.<имя>…<имя>. В версии-0 не поддерживается и читайте просто <имя>.
Привязка к реализации (далее <привязка>)
Указание привязки к реализации для методов (функций и процедур) передается в строке определения в виде ... := <имя>. Это <имя> ищется у ОбъектаПлатформы и проверяется на соответствие соглашению о формате.
Функции и процедуры указанные в привязках должны иметь вид:
ФУНКЦИЯ <имя>(ЗНАЧ <класс> [, <остальные-параметры-если-есть>])
или
ПРОЦЕДУРА <имя>(ЗНАЧ <класс> [, <остальные-параметры-если-есть>])
Для свойств указание на привязку к реализацию задается в виде:
ЧТЕНИЕ := <имя-функции-чтения> ЗАПИСЬ := <имя-процедуры-записи>.
Функция чтения свойства должна иметь вид ФУНУЦИЯ <имя> (ЗНАЧ <класс>) и возвращать значение свойства.
Процедура записи должна иметь вид ПРОЦЕДУРА <имя> (ЗНАЧ <класс>, НовоеЗначениеСвойства). В параметр НовоеЗначениеСвойства передается значение, которое присваивается свойству в программе.
Одно из указаний на реализацию может быть опущено. Если не указано ЗАПИСЬ, то свойство становится ТолькоДляЧтения. Если не указано ЧТЕНИЕ, то свойство становится ТолькоДляЗаписи. Опускание и ЧТЕНИЕ и ЗАПИСЬ не допускается.
При вызове нестатических свойств и методов компонента в аргумент <класс> помещает внутренний контекст объекта экземпляра класса – ЭтотКласс.
При вызове статических свойств и методов в аргумент <класс> компонента помещает внутренний контекст объекта определителя класса – МойОпределитель.
Для абстрактных свойств и методов (помеченных атрибутом АБСТРАКТН(АЯ|ОЕ)) указание привязки недопустимо. Для абстрактных свойств допустимо указывать атрибуты ТолькоЧтение\ТолькоЗапись. Если атрибут опущен, то свойство считается доступным и для чтения и для записи. При задании привязки к реализации в классе наследнике допустимо указывать только привязку разрешенную атрибутом <только-чтение-запись> абстрактного свойства.
PS: В текущей реализации ЭтотКласс и МойОпределитель это, конечно, просто имена переменных и могут быть любыми. Но настоятельно рекомендую использовать именно эти термины. Хотя это и близко к невозможному (а скорее очень трудоемко), но нельзя исключать, что в будущем они станут терминами языка. PPS: сами термины обсуждаемы, но рекомендация унификации остается.
Общее описание атрибутов
Атрибут СТАТИК означает, что элемент с этим атрибутом един для всех экземпляров этого класса и не имеет доступа к данным экземпляра класса, а только к таким же статическим элементам класса. По сути, статический элемент - это элемент объекта определителя класса, но присутствует и в контекстах экземпляра класса. В методы реализации статических элементов указанные в привязке первым параметром передается внутренний интерфейс определителя класса, а не внутренний интерфейс экземпляра класса. Таким образом статические элементы имеют доступ к другим статическим элементам класса, но не к элементам экземпляра класса. Публичные статик элементы доступны через обращение к определителю класса через объект компоненты. Совпадающие по определению статические свойства и методы наследника скрывают свойства и методы базового в контексте наследника.
Версия-0: первичная инициализация статических переменных не реализована. После создания объекта-определителя класса, функция компоненты ОпределитьКласс(…) возвращает приватный интерфейс определителя давая возможность провести первичную инициализацию внутренних статических переменных, если это необходимо.
Атрибут ПЕРЕГРУЗКА применим к функциям и процедурам и означает создание более одного метода с одинаковым именем, но отличающихся количеством и\или типом данных аргументов. При вызове метода класс, на основании количества и фактического типа данных переданных параметров, переадресует вызов соответствующему методу реализации. Поддерживаются типы данных платформы ЧИСЛО, СТРОКА, ДАТА, БУЛЕВО, НЕОПРЕДЕЛЕНО, NULL, ТИП, ОБЪЕКТ. Также может быть использован термин ЛЮБОЕ, означающий что данный аргумент метода может принимать значение любого типа. Если тип не указан, то считается ЛЮБОЕ. Согласованность всего букета перегрузок проверяется для недопущения неоднозначного поведения. Атрибут ПЕРЕГРУЗКА обязательно явно использовать во всех вариантах метода, даже если он один, но вы допускаете его перегрузку у наследников. Атрибут трактуется как атрибут имени.
При сокрытии или замещении букета перегрузок из базового класса в наследнике всегда скрывается весь букет. Приоритет имени. Для «проявления» перегрузок из базового класса в наследнике их нужно явно определять. Версия-0: конструкция ИСПОЛЬЗОВАТЬ <…> не поддерживается.
Ограничение методики: Интерфейсы 1С не поддерживают концепцию перегрузки методов. Перегрузка реализована компонентой с использованием особенностей работы со значениями по умолчанию. Поэтому использование значений по умолчанию для последних, непересекающихся с остальными вариантами, аргументов принципиально невозможна. Для пересекающейся части аргументов возможна, но только одинаково для всех вариантов. Пример: Фуу(а1_) ПЕРЕГРУЗКА; Фуу(а1_, а2_) ПЕРЕГРУЗКА. Для а2_ значения по умолчанию невозможны. Для а1_ возможны, но только одинаково у всех вариантов.
Ограничения версии-0: а) Количество аргументов – 16. б) Алгоритм проверки согласованности, если указано ЛЮБОЕ, исключает другие типы данных по данному параметру, а не дополняет существующие.
Атрибуты замещения ЗАМЕЩАЕМ[АЯ|ОЕ], ЗАМЕЩАЕТ, ЗАМЕЩАТьОБЯЗАТЕЛЬНО, НеЗАМЕЩАТЬ, АБСТРАКТН[АЯ|ОЕ] . Определяют замещаемый (виртуальный) метод или свойство. Реализация указанная в наследнике замещает реализацию указанную у базовых классов. Таким образом реализация свойства или метода у класса единственна и определяется наследноком. Для абстрактного метода\свойства реализация не указывается. Вызов абстрактного метода приводит к ошибке исполнения. В остальных случаях реализация должна быть указана обязательно. При определении интерфейса указывать атрибут абстрактности обязательно. Просто атрибут ЗАМЕЩАЕМ[АЯ|ОЕ] определяет метод как замещаемый и проводит процедуру замещение неуспех которой не является ошибкой. ЗАМЕЩАЕТ требует чтобы метод обязательно заместил что-либо или ошибка. ЗАМЕЩАТьОБЯЗАТЕЛЬНО указывет, что при определении наследника класса он должен обязательно заместить этот метод своей реализацией. НеЗАМЕЩАТЬ, наоборот, запрещает замещать данный метод наследниками. Возможно сочетание атрибутов – Замещает НеЗамещать, Замещает ЗамещатьОбязательно.
При указании атрибута АБСТРАКТН[АЯ|ОЕ] указание привязки к реализации считается ошибкой.
Версия-0: При создании экземпляра класса наличие не реализованный абстрактных элементов не контролируется. При вызове свойства или метода без реализации выбрасывается ошибка.
Если метод или свойство не имеет атрибутов СТАТИК или атрибутов замещения, то он считается ОБЫЧНЫМ (или скрываемым) методом экземпляра класса. Метод указанный у наследника скрывает собой метод базового класса в контексте наследника, но не замещает его в контексте базового класса. Если имя метода определяет букет перегрузок, то скрыты будут все перегруженные методы.
(версия-0: напрашиваются атрибуты НеСкрывать\СкрыватьОбязательно)
Области видимости элемента (далее <область>)
Область видимости (ВНУТР | ЗАЩИЩ[(Е|Ё)Н] | ЭКСПОРТ) влияет на присутствие элементов в контекстах класса:
- Публичная (термин ЭКСПОРТ) – элемент виден всем всегда и во всех контекстах.
- Внутренняя (термин ВНУТР) – элемент виден только через внутренний контекст ЭтотКласс
- Защищенная (термин ЗАЩИЩ(Е|Ё)Н) – элемент виден этому классу и его наследникам, но не через публичный контекст. Т.е. унаследовано присутствуют в контексте ЭтотКласс наследника и непосредственно в контексте возвращаемом свойством МойБазовый.
Может задаваться как секцией так и атрибутом определения элемента (версия-0: склоняюсь атрибут убрать и оставить только секции, т.к. может приводить к путанице. Или сделать атрибуты опцией языка.).
Имя секции задается термином области видимости с двоеточием в конце. Область видимости заданная атрибутом элемента имеет приоритет перед заданной секцией. По умолчанию, с начала определения класса секция устанавливается в ЭКСПОРТ.
Область видимости – это для точки вызова. Не для компилятора. Компилятор «видит» все. Заместить внутренний замещаемый элемент базового класса из производного можно, а вызвать нельзя.
Синтаксис строк определения элементов
Общий вид строки определения элемента: <определение-…> [,] <атрибуты-определения> [:= <привязка>] [;]
<определение-начало-класса>
[<атрибуты-класса>] КЛАСС <имя-класса> [НАСЛЕДУЕТ <идентификатор>]
Наследование сделано не прототипно. Т.е. базовый класс как самостоятельный объект «там» нигде не существует. В терминах 1С при объявлении базового класса происходит начальная инициализация «метаданных» класса «метаданными» базового класса. На данный момент наследование сделано одиночным (т.е. можно унаследоваться только от одного класса) и только публичным (т.е. все публичные методы базового класса становятся доступны через публичный интерфейс производного класса, в версии-0 изменение области видимости элементов не поддерживается). В принципе, применяемая модель позволяет делать и закрытое, и множественное и даже множественное виртуальное наследование.
<атрибуты-класса>
ДляНАСЛЕДОВАНИЯ – запрещает создание экземпляра класса. От такого класса можно только наследоваться.
НеНАСЛЕДУЕМЫЙ – от такого класса запрещено наследование.
<определение-конец-класса>
КОНЕцКЛАССА
В теле определения класса должно быть определение хотя бы одного элемента класса. Пустые классы не допускаются.
<определение-начало-интерфейса>
[<атрибуты-интерфейса>] ИНТЕРФЕЙС <имя> [НАСЛЕДУЕТ <имя>]
Допустимо вне тела другого определения. Версия-0: Вложенные определения не поддерживаются. Трактуется как начало определения нового интерфейса и до определения КонецИнтерфейса могут быть только декларации свойств, функций и методов. Указание атрибута АБСТРАКТН[АЯ|ОЕ] для элементов интерфейса обязательно. Элементами интерфейса могут быть только свойства, функции и процедуры. Переменные в интерфейсе недопустимы.
<атрибуты-интерфейса> не предусмотрены.
<определение-конец-интерфейса>
КОНЕцИНТЕРФЕЙСА
Обозначает конец определения объекта интерфейс или конец секции реализации интерфейса внутри класса. В случае конца определения интерфейса контролируется определения хотя бы одного элемента – пустые интерфейсы не допускаются (версия-0: надо разрешить). В случае конца секции реализации интерфейса полнота реализации не контролируется – допустимо реализовать интерфейс частично и окончательно реализовать его в наследнике.
<определение-реализации-интерфейса>
РЕАЛИЗУЕТ [ИНТЕРФЕЙС] <имя>
Означает начало секции реализации интерфейса классом. Продолжается до определения КонецИнтерфейса. Внутри этой секции описываются привязки абстрактных элементов интерфейса к конкретным методам реализующим их функциональность. Например, если в интерфейсе было определение Функция Фуу() Абстрактная, то в секции реализации должно быть определение Функция Фуу() <атрибут-замещения> := <привязка-реализации>. Реализация интерфейса должна находиться в публичной секции. Реализация интерфейсов внутренне или защищенно не предусмотрено (версия-0). Элементы интерфейса не доступны через контекст самого класса. Для доступа к ним необходимо вызвать метод .ДайИнтерфейс(<имя>), который возвращает контекст со свойствами и методами описанными в определении интерфейса. Или неопределенно, если класс не реализует запрошенный интерфейс. Если реализуемый интерфейс в свою очередь унаследован от других интерфейсов, то его реализация его свойств и методов автоматически приводит к неявной реализации соответствующих свойств и методов его базовых интерфейсов. Допустимо создавать несколько секций реализации интерфейса.
Версия-0: если у класса уже есть реализация одного из базовых интерфейсов реализуемого интерфейса (непосредственно или унаследовано), то генерируется ошибка; также, попытка реализации базового интерфейса, а не наследника приводит к ошибке.
<определение-переменная>
ПЕРЕМ <имя> [,] [<атрибуты-переменной>]
Объявление переменной. Допустимо только при определении класса и только вне блока реализации интерфейса.
<атрибуты-переменной> ::= [СТАТИК] [ТолькоЧТЕНИЕ | ТолькоЗАПИСЬ][<область-видимости>]
Если не указаны атрибуты <только-чтение-запись>, то переменная доступна и для чтелия и для записи.
<определение-свойство>
При определении интерфейса:
СВОЙСТВО <имя> [,] АБСТРАКТНОЕ [<только-чтение-запись>][<область>]
где
<только-чтение-запись>::= ТОЛЬКОЧТЕНИЕ | ТОЛЬКОЗАПИСЬ
При определении класса:
СВОЙСТВО <имя> [,] [<модификатор>] [<область>] [<привязка-свойства>]
СВОЙСТВО <имя> [,] АБСТРАКТНОЕ [<только-чтение-запись>][<область>]
где
<привязка-свойства>::= [ЧТЕНИЕ := <имя>] [ЗАПИСЬ := <имя>]
<определение-функция> и <определение-процедура>
ФУНКЦИЯ <имя> [,] [ПЕРЕГРУЗКА] [<модификатор>] [<область>] [<привязка>]
ПРОЦЕДУРА <имя> [,] [ПЕРЕГРУЗКА] [<модификатор>] [<область>] [<привязка>]
где
<модификатор> ::=
АБСТРАКТН(АЯ|ОЕ)
| СТАТИК
| (ЗАМЕЩАЕТ | ЗАМЕЩАЕМ(АЯ|ОЕ) | НЕЗАМЕЩАТЬ | ЗАМЕЩАТЬОБЯЗАТЕЛЬНО)
<привязка>::=
:= <имя>
<определение-конструктор>
КОНСТРУКТОР ([<список-аргументов>]) [ПЕРЕГРУЗКА] [<привязка>]
КОНСТРУКТОР ()
Привязка конструктора должна быть процедурой и указывать ее обязательно. Допустимо не указывать привязку для конструктора без аргументов и атрибута перегрузки при размещении его во ВНУТР/ЗАЩИЩЕН секции. В этом случае строка определения трактуется как переопределение области видимости конструктора по умолчанию. Таким способом определяется не создаваемый публично класс. Публично не создаваемые классы не добавляются к списку методов свойства компоненты .СоздатьКласс и не имеют статического метода .СоздатьКласс() в публичном контексте определителя класса. Метод .СоздатьКласс() присутствует только во внутреннем контексте определителя класса и, таким образом, доступен для статических свойств и методов класса. Унаследоваться от такого класса тоже нельзя.
Перед вызовом конструктора все переменные экземпляра класса устанавливаются в неопределенно. Вызывается конструктор самого младшего наследника. Синтаксиса делегирования вызова конструктора в версии-0 нет. Поэтому конструкторы базовых классов компонентой не вызываются! При необходимости вызова явно объявленного конструктора базового необходимо вызывать его вручную в процедуре конструктора класса в виде ЭтотКласс.МойБазовый.Конструктор([<параметры-конструирования-базового>]);. Вызван ли реально конструктор базового класса компонента не контролирует. На момент вызова конструктора класс уже полностью сконструирован и все замещаемые элементы замещены. В любом случае старайтесь избегать вызова виртуальных методов из конструктора и деструктора. При возникновении исключения в процедуре конструктора компонента перехватывает его, очищает переменные, удаляет выделенную память и пробрасывает исключение платформе. Что примечательно, платформа в этом случае его корректно перехватывает и краха не происходит. В отличие от деструктора – там платформа явно не готова к замысловатому сценарию после .Release(). Также, если вы успели сохранить ЭтотКласс в какой-либо переменной, после исключения в конструкторе крах неизбежен. Планирую перейти на передачу слабой ссылки на ЭтотКласс, как ужк сделано в деструкторе. В любом случае – старайтесь не допускать (не выпускать) исключений в конструкторе и деструкторе.
< определение деструктор>
ДЕСТРУКТОР () <привязка>
Привязка в определении деструктора обязательна.
Когда счетчик ссылок экземпляра класса достигает нуля компонента вызывает определенные для класса процедуры деструкторов начиная с наследников и к базовым. После этого компонента дополнительно принудительно очищает все переменные класса и освобождает память. В качестве значения ЭтотКласс процедуре деструктору передается специальный объект СсылкаНаКласс через который можно точно также получать доступ ко всем элементам класса. По выходу из тела процедуры деструктора ссылка, даже будучи сохраненной в какой-либо переменной, очищается и превращается в ПустуюСсылку. Попытка вызвать какие-либо элементы класса через пустую ссылку приводят к ошибке. ПустуюСсылку можно только сравнивать с объектом возвращаемым свойством компоненты .ПустаяСсылка. Класс удаляется вне зависимости от сохраненных где-либо ссылок на него.
Версия-0: Процедура деструктор не должна выбрасывать исключения!!! Приводит к краху на втором вызове деструктора!!!
<определение-секции-области-видимости>
ЭКСПОРТ:
ЗАЩИЩЕН:
ВНУТР:
Действие распространяется на все элементы класса до определения следующей секции или до конца класса. В начале класса по умолчанию устанавливается в ЭКСПОРТ.
<комментарии>
//<любой-текст-до-конца-строки>
Задумано, но не реализовано
- Наитивная поддержка коллекций 1С. Для языка обрабатывающего данные это очень важно.
- Первичная инициализация статических членов.
- Комментарии в строке.
- Переопределение области видимости элементов.
- привязка свойства на свойство
- Множественное наследование. Важно для реализации аццессоров коллекций.
- Языковая конструкция ИСПОЛЬЗОВАТЬ.
- Операторы. Есть основания полагать, что интерфейсы 1С позволяют реализовать концепцию операторов «сравнение» и операторов преобразования_к_ «БУЛЕВО», «СТРОКА», «ДАТА», «ЧИСЛО».
- Делегирование вызова конструктора базового класса.
- расширенные атрибуты.
- декларирование функций ос и сторонних dll.
- расширение алгоритма определение перегрузки методов до интерфейсов объектов.
- Свободные свойства\методы, или свойства\методы расширения. На данный момент все привязки обязаны соответствовать определенной сигнатуре. Возможно к контекстам класса подключать свойства\методы без требования на первый аргумент. Возможно это новая языковая конструкции МОДУЛЬ.
- Типизация. Расширять применение вплоть до дженериков и до проверки типов данных методов привязки.
- Методы по умолчанию. Если метод с именем <имя> в контексте отсутствует, но есть свойство с именем <имя> и это объектное свойство с методом по умолчанию то вызываем этот метод. Естественными кандидатами на такие объектные свойства являются делегаты, объекты ТИП, и объекты имеющие метод с атрибутом МетодПоУмолчанию. До концепции typedef. Обратная ситуация – использование имени метода как свойства – естественная конструкция для возврата делегата метода. Не разрешать и разрешать только если явно указано определение соответствующего свойства.
- обвязка тестами
- демонстрационная реализация паттернов
- <вы можете дополнить (а лучше уменьшить) этот список>
Статус разработки.
Компонента реализует расширение языка, а язык должен быть бесплатным. Поэтому:
Использование представленной в статье компоненты в познавательных, образовательных и коммерческих целях бесплатно. Автор не исключает возможных коммерческих расширений технологии как самим автором, так и сторонними разработчиками при соблюдении условий бесплатности самого языка и ненанесении вреда коммерческим интересам компании 1С и ее деловой репутации. В случае использования в коммерческих приложениях автор готов заключить «договор с законным обладателем копии программы 1С по доработке ее в соответствии с ее функциональностью и без вмешательства в исходный код программы». В этом случае, при необходимости обеспечения юридических формальностей, автор оставляет за собой право устанавливать стоимость договора в пределах издержек автора на обеспечение юридической схемы.
В рамках своих возможностей, по мере накопления улучшений, исправлений и доработок я буду выкладывать их в публичном доступе.
В то же время, особенно на текущем этапе, поддержка и развитие технологии требует определенных затрат времени автора, которые он не может обеспечить без ущерба для себя. Поэтому автор просит, всех кто находит предложенную технологию интересной и полезной, о пожертвовании на ее разработку и совершенствование, в рамках индивидуального восприятия ее ценности.
Почта: v8classes@gmail.com
WMU: U110651501027
WMR: R444928807260
WMZ: Z529168937078
Спасибо.