Как стать автором
Обновить

ULCA. Новый объектный язык программирования

Уровень сложностиСредний
Время на прочтение28 мин
Количество просмотров4.8K

Данная статья является продолжением серии статей посвященных Системе разработки клиентских приложений (KISS Virtual XML DBCS). В этой статье рассматривается объектная модель, используемая языком программирования ULCA (Universal Language for Client Application).

Язык ULCA использует лишь объектные конструкции, а написанное на нем Приложение представляет всего лишь набор классов, т.е. данный язык является чисто объектным.

Язык ULCA - это интерпретируемый платформенно-независимый язык программирования. Интерпретатор для большинства декларативных языков программирования написан на каком-нибудь С-подобном языке или на любом другом платформенно-независимом языке, что и обеспечивает кросс-платформенность этих декларативных языков. Язык ULCA не является исключением.

Интерпретатор языка ULCA включает в себя следующие два компонента:

  • Универсальный Конструктор объектов. Исходными данными для него являются определения классов.

  • Интерпретатор программного кода. В качестве исходных данных используется откомпилированный текст процедур (P-код).

В этой статье мы рассмотрим объектную модель, которая является основой языка ULCA. Именно объектная модель определяет возможности и синтаксис языка ULCA. Синтаксис языка ULCA будет рассмотрен в следующей статье. Автор Статьи предполагает, что Вы знакомы с понятиями и возможностями объектно-ориентированного программирования.

Статья разбита на две части.

В первой части предельно кратко рассмотрим основные понятия, используемые как в языке ULCA, так и в других объектно-ориентированных языках. Первая часть предназначена, в основном, для того, чтобы Вам быстрее войти в тему (для "разогрева") и было бы проще воспринимать вторую часть.

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

Часть 1. ООП и Язык ULCA. Классика

Пространство имен.

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

Объект

Объект - это автономная совокупность переменных и процедур, используемая для формального представления реального объекта. Переменные объекта служат для хранения значений характеристик объекта, поэтому их принято называть свойствами объекта. Свойства и процедуры объекта называют элементами объекта.

Процедуры объекта по своему назначению и способу вызова делятся на методы, события и функции.

Процедуры-методы предполагают выполнение вполне определенных действий. Эти действия могут быть выполнены только при явном вызове на выполнение процедуры метода.

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

Процедуры-функции - это методы, имена которых являются глобальными, поэтому их можно вызывать без указания их физического размещения (пути до объекта, в котором они определены), т.e. они вызываются на выполнение так же, как это принято в функциональных языках. Еще одной важной особенностью функций, является то, что они не имеют собственного контента. В качестве контента функции используется контент процедуры, из которой данная функция была вызвана.

Физически объект можно рассматривать как область оперативной памяти, в которой размещены указатели на свойства и процедуры объекта. Эту область памяти принято называть интерфейсом объекта.

Доступ к свойствам и процедурам объекта возможен только через ссылку на объект. Ссылка на объект - это адрес интерфейса объекта.

Поскольку обращение к свойствам и процедурам объекта осуществляется только посредством указателя на объект, имеется возможность управлять доступом к элементам объекта и контролировать корректность их использования. Имена элементов объекта должны быть уникальны внутри интерфейса и образуют локальное пространство имен объекта.

В объект можно добавлять другие объекты. При этом автоматически создается новый вложенный объект, добавляется в родительский объект свойство ссылочного типа с именем нового объекта и присваивается этому свойству ссылку на вложенный объект (адрес интерфейса вложенного объекта). Таким способом формируется дерево вложенных объектов. У каждого вложенного объекта имеется обратная ссылка на родительский объект, которая автоматически формируется при добавлении вложенного объекта.

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

В тексте процедуры обращение к элементам объекта выглядит следующим образом:

oRefer.MembName[()], где:

oRefer - имя переменной-ссылки на объект

MembName - имя элемента объекта (свойства, вложенного объекта или процедуры)

Для обращения к элементу объекта используется точка (dot-нотация) и имя элемента.

Оператор точка означает доступ к интерфейсу объекта

Если MembName является именем свойства-ссылки или именем процедуры, возвращающей ссылку на объект, то цепочку обращения можно продолжать до тех пор, пока удовлетворятся указанное выше условие:

oRefer.MembName[()].MembName.MembName...

Таким образом, присвоив переменной oRefer ссылку на какой либо объект, Вы получаете доступ ко всем элементам как этого объекта, так всех связанных с ним объектов. Переменная oRefer содержит начальную ссылку (якорь).

Для облегчения получения начальной ссылки на объект предусмотрены системные переменные ссылочного типа my, This и ThisForm, значения которым присваиваются автоматически и которые доступны только для чтения:

my - это единственная глобальная переменная. Она содержит ссылку на объект application, который является вершиной объектной структуры и позволяет "добраться" до любого созданного объекта.

This и ThisForm - это локальные переменные, которым автоматически присваиваются ссылки на текущий объект или текущую форму в момент вызова процедуры на выполнение.

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

Классы и наследование.

Класс объекта - это определение объекта, которое хранится в библиотеке классов в виде текста. Определение объекта представляет собой формальное описание всех свойств и процедур объекта, а так же определение вложенных в него объектов (если таковые имеются). При создании объекта обязательно указывается имя класса создаваемого объекта. Созданный объект будет иметь тот состав свойств, процедур и вложенных объектов, который определен в указанном классе.

Таким образом, класс - это инструкция, на основании которой создается экземпляр объекта.Все элементы объекта, созданные на основании определения класса принято называть статическими. Эти элементы не могут быть удалены из объекта программным путем. В сознанный объект могут быть программным путем добавлены новые свойства и объекты, которые принято называть динамическими. Динамически созданные элементы объекта могут быть удалены только программным путем. Такой способ разделения полномочий между статически и динамически созданными элементами объекта позволяет избежать конфликта при их совместном использовании.

На основании любого класса можно создать произвольное число дочерних классов, которые автоматически унаследуют объектную структуру и все характеристики родительских классов. В дочернем классе можно добавить новые объекты, свойства и процедуры, скрыть существующие элементы объекта или ограничить к ним доступ (удалить элементы родительского класса нельзя!), а также переопределить процедуры и свойства объектов родительского класса. Переопределение процедуры принято называть перезагрузкой процедуры (overload). Это очень неудачное название, поэтому в дальнейшем будем использовать слово "переопределение". Переопределение процедуры не приводит к удалению родительской процедуры. Родительская процедура остается доступной для выполнения в качестве процедуры по умолчанию. Не следует увлекаться многократным переопределением процедур. Это может сильно усложнить сопровождение. Созданный класс запоминается в библиотеке соответствующего базового класса с именем, указанным пользователем. Полное имя класса имеет следующий вид:

<имя базового класса>.<имя класса>

Имя класса должно быть уникальным в рамках базового класса. Таким образом, базовый класс является пространством имен классов, созданных на основании данного базового класса.

Содержимое библиотеки классов
Содержимое библиотеки классов

Часть 2. Язык ULCA. Классика+

Базовые типы данных

Язык ULCA является жестко типизированным. Состав допустимых типов данных минимальный и не может быть пополнен. Это обеспечивает совместимость по данным со всеми объектно-ориентированными языками программирования (в том числе и со скриптовыми языками программирования, такими, как JavaScript и PHP), а таже с типами данных внешних источников данных (реляционные базы данных) . Для обозначения типа данных используется одна прописная латинская буква.

Состав базовых типов данных языка ULCA:

  • c - char - строка символов в любой кодировке

  • n - numeric - вещественное число

  • i - integer - целое число

  • l - logical - логическое значение

  • d - date - дата

  • t - time - время

  • s - stamp - штамп (дата и время)

  • o - object - ссылка на объект

  • a - array - ссылка на массив

  • f - function - ссылка на процедуру

  • u - variant - неопределенное значение

Данная статья посвящена объектной модели, используемой языком ULCA, поэтому нас здесь интересуют только ссылочные типы данных (object, array и function). Типу данных object будут посвящены практически все оставшиеся разделы статьи, а вот типы данных array и function есть смысл рассмотреть в этом разделе.

Ссылка на массив

Массив представлен в языке ULCA в качестве базового типа данных, а не в виде объекта, как это сделано во многих других языках. А это значит, что весь функционал для работы с массивами представлен не в виде методов, а в виде функций, в которых первым параметром указывается имя массива. Тем не менее, массив относится к ссылочным типам и работа с массивом похожа на работу с объектом:

  • массив передается в вызываемую процедуру по ссылке;

  • массив может быть использован в качестве возвращаемого значения процедуры;

  • массив можно включать в качестве элемента в другой массив (многомерный массив), хотя практическая польза использование многомерных массивов в клиентском приложении очень сомнительна.

Массив представляет собой именованную совокупность пронумерованных переменных. Переменные массива (элементы массива) имеют тип данных variant, т.е. могут содержать данные любого типа (в том числе и массивы). Каждому элементу присваивается порядковый номер (индекс) начиная с единицы (в большинстве других языков программирования нумерация начинается с нуля). Обращение к элементу массива (для записи или чтения) происходит по его номеру. Несмотря на кажущуюся простоту, массив являются одним из самых неоднозначных понятий. В различных языках массив интерпретируется по-разному. С точки зрения клиентского приложения самым естественным является представление массива в виде таблицы (в виде строк и колонок). Одномерный массив рассматривается как таблица с одной колонкой. Именно такое представление массива используется в качестве основного. Физически массив представляет собой одномерный массив, который логически разбит на целое число одинаковых порций элементов (строк массива). Количество строк и количество колонок (размер порции элементов) запоминаются в дескрипторе массива, который используется для интерпретации обращения к элементам массива.

Ссылка на процедуру

Ссылка на процедуру представляет собой объектную оболочку над процедурой, содержащую адрес процедуры (точку входа в нее). Это позволяет передавать процедуру в качестве аргумента другим процедурам и использовать ее в качестве возвращаемого значения. Ссылки на процедуры чаще всего используются в качестве инструмента для вызова callback процедур.

Фундаментальные элементы объекта

Существует общий для всех объектов набор элементов (свойств и процедур). Эти элементы будем называть фундаментальными элементами объекта. Они не включаются в состав собственных элементов объекта, а являются элементами другого объекта - подложки (substrate). Таким образом, любой объект имеет объектную подложку, интерфейс которой содержит ссылки на фундаментальные элементы объекта.

Может возникнуть вопрос: зачем объекту нужна подложка? Почему бы не включить фундаментальные элементы непосредственно в сам объект?

Если не отделить фундаментальные элементы от всех остальных элементов, то будет невозможно получить пустой объект (empty), который является основой всех классов объектной модели. Подложка определяет собственное, независимое от самого объекта пространство имен, поэтому в объект можно добавлять элементы с именами, совпадающими с именами фундаментальных элементов.

Обращение к элементам подложки объекта осуществляется с использованием оператора <.:>, например:

oRefer.:Parent

Список основных элементов подложки объекта:

свойства:

  • c .:AsClass - имя квази-родительского класса

  • c .:BaseClass - имя базового класса

  • c .:Class - имя класса

  • c .:ParentClass - имя родительского класса

  • o .:Parent - ссылка на родительский объект

  • c .:Name - имя объекта

  • l .:Instance - признак объекта instance

  • c .:Status - статус объекта

  • c .:Path - полный путь до объекта

  • c .:Comment - любые комментарии

события:

  • l .:Init() - возникает сразу после создания объекта

  • l .:Destroy()- возникает перед удалением объекта

  • c .:Error() - возникает при возникновении ошибок в процедурах объекта

методы:

  • o .:AddObject(cObjName,cClass[,uInitPar[,...]] - добавить новый объект

  • l .:AddProperty(cPropName,cDataType[,uValue])- добавить новое свойство

  • l .:Remove(cMembName) - удалить элемент объекта

  • u .:Get(MembName[,uParm[,...]]) - вернуть значение элемента

  • l .:Set(cPropName,uValue) - присвоить значение свойству

  • l .:Reset(cMembName) - восстановить значение по умолчанию

  • l .:Refresh() - обновить значения привязанных свойств

  • o .:Siblings([cMembFilter]) - получить коллекцию вложенных объектов

  • l .:Order([iOrder[,cObjName]] - изменить Z-позицию данного объекта или вложенного в него объекта

Фундаментальные элементы свойства и процедуры

Свойства и процедуры объекта мы называем элементами объекта, хотя эти элементы сами представляют собой достаточно сложные образования. Является фактом, что у свойства тоже есть свойства. Для свойств и процедур предусмотрены объектные подложки, содержащие фундаментальные свойства и процедуры для этих элементов. Подложки обеспечивают удобное обращение ко всем свойствам, методам и событиям элементов объекта, причем чисто объектным способом.

Таким образом, свойства и процедуры рассматриваются как объекты со своим собственным интерфейсом в виде подложек!

Обращение к элементам подложки (интерфейсу) свойства или процедуры объекта осуществляется с использованием символа двоеточия (colon-нотация). Этот символ ставится сразу после имени элемента, например:

oRefer.ProcName:DataType

Элементы подложки тоже могут иметь подложки. Цепочка ссылок на элементы подложек ограничена правилом "двух двоеточий", которое гласит, что цепочка ссылок на подложки не может содержать более двух двоеточий. Есть и другие ограничения, не такие формальные, как указанное выше правило, а именно:

  • свойство подложки не имеет подложки,

  • процедура подложки имеет подложку, состоящую только из свойств (без процедур).

Указанные выше правила введены для того, чтобы предотвратить бесполезную рекурсию при обращении к элементам подложки.

Подложка свойства

Список основных элементов подложки свойства:

события:

  • u :Access([iRow[,iCol]]) - возникает при чтении значения свойства

  • l :Assign(uValue[,iRow[,iCol]) - возникает при присвоение нового значения

свойства:

  • c :Name - имя свойства

  • c :MemberType - тип свойства

  • c :As - список допустимых классов

  • c :DataType - тип данных свойства

  • u :DefaultValue - значение по умолчанию

  • c :Length - определение размерности свойства

  • c :Tip - список допустимых значений

  • c :Inherited - имя наследуемого класса

  • c :Status - статус свойства:

  • c :Comment - любые комментарии

  • c :BoundTo - путь до привязанного свойства или выражение

  • u :Value - текущее значение свойства

свойства-признаки:

  • l :Changed - признак измененного значения

  • l :Null - признак нулевого значения

  • l :NullAllowed - признак допустимости нулевого значения

  • l :Protected - признак запрета переопределения

  • l :ReadOnly - признак "только чтение"

  • l :Hidden - признак скрытого свойства

  • l :Unsigned - признак положительного числа

Появление подложки свойства - это одно из самых важных приобретений объектной модели языка ULCA. Наличие подложки свойства позволило совместить, казалось бы, несовместимые модели - XML-модель данных (структура произвольного уровня вложенности) и реляционную модель данных (линейный список полей записи). Подробнее об этом поговорим позднее при обсуждении объекта базового класса element, а в этом разделе мы рассмотрим только выделенные жирным шрифтом  элементы. Назначение остальных элементов, скорее всего, не вызовет у Вас вопросов.  

Нулевое значение (:Null)

Под нулевым значением понимается отсутствие значения (не указано, неизвестно, нет значения). Не нужно путать нулевое значение с пустым значением. Пустое значение - это одно из возможных значений, а не отсутствие такового.

Во всех объектно-ориентированных языках нулевое значение свойства (обозначается ключевым словом null) рассматривается как оно из возможных его значений. В языке ULCA нулевое значение не является одним из возможных значений свойства, а реализовано в виде признака нулевого значения, который хранится в подложке свойства отдельно от его значения. В языке ULCA нулевое значение свойство - это пустое значение свойства (:Value) с установленным признаком нулевым значения (:Null). Для того чтобы присвоить свойству нулевое значение, обходимо просто установить признак :Null:

oRefer.PropName:Null = true

При этом автоматически присваивается свойству (:Value) пустое значение. Признак нулевого значения можно сбросить вручную, или это автоматически произойдет при присвоении свойству любого непустого значения.

Зачем нужно было убирать нулевое значения из допустимых значений свойства? Проблема возникает при использовании нулевых значений в выражениях. Результат оказывается непредсказуемый. Никто не может однозначно объяснить, как будут интерпретированы операнды выражения с нулевыми значениями. На языке ULCA в выражениях нулевые значения представлены пустыми значениями, поэтому никаких проблем не возникает.

Остался нерассмотренным только вопрос с пустым значением для ссылки на объект. Дело в том, что у ссылки нет своего пустого значение - в качестве его используется нулевое значение. Пришлось создать значение, которое можно использовать в качестве пустого значения ссылки. Основой базовой модели языка ULCA является базовый класс empty. Имеется только один объект empty, автоматически создаваемый при запуске Приложения на выполнение. Ссылка на этот объект присваивается именованной константе nothing, значение которой и служит признаком пустой ссылки. Таким образом, пустая ссылка - это адрес пустого объекта. Такой способ формирования пустой ссылки позволяет проверить отсутствие ссылки простым сравнением значения ссылки с константой nothing, а также присвоить пустое значение ссылке с помощью операции присвоения значения nothing., например:

if oRefer == nothing

 * пустая ссылка

else

 * присвоить ссылке пустое значение

 oRefer = nothing

endif

Связывание (:BoundTo)

Связывание (binding) является мощным средством декларативно программирования, поэтому подробное обсуждение его возможностей и области применения в отрыве от средств разработки (IDE) очень трудно.

Попытаемся, очень коротко, дать представление об этом понятии.В объектно-ориентированных языках связывание реализуется исключительно императивными средствами, например c помощью функций bind(), bindevent() или с помощью объекта класса Binding (C#).

Наличие подложек у свойств и процедур позволило с помощью свойства :BoundTo  определять связанные элементы (свойства и процедуры) декларативными средствами. Свойство :BoundTo содержит путь до привязанного свойства или выражения. Путь до привязываемых свойств должен начинаться с относительной ссылки This и включать объекты только внутри редактируемого класса. Это обеспечивает максимальную независимость этого пути от окружающего данный объект контента.

Используются следующие виды связывания:

  • Переадресация свойства:

При обращении к данному свойству, вы фактически обращаетесь к указанному привязанному свойству (чистая переадресация) для чтения или корректировки его значения.

  • Связь с источником данных:

Отличается от чистой переадресации тем, что при обращении к свойству возвращается значения самого этого свойства, а не значение связанного с ним свойства (источника данных). Чтобы обновить значение свойства текущим значением источника данных, необходимо вызвать метод .:Refresh() подложки объекта, в котором определено свойство. При изменении значения свойства, это значение автоматически присваивается источнику данных. Данный вид связи применяется к доступным для записи свойствам визуальных объектов (чаще всего, к свойству .Value)

  • Связь с выражением:

Вместо одной ссылки на связанное свойство можно указать выражение, составленной из одного или нескольких связанных свойств, т.е. определить вычисляемое свойство, которое при обращении к нему вернет вычисленное выражение. Причем само свойство становится доступным только для чтения!

Подложка процедуры

Следующие элементы подложки доступны только внутри выполняемой процедуры, поскольку их использование имеет смысл только во время выполнения процедуры.

  • i :ArgCount - возвращает количество переданных аргументов

  • i :ArgNo(cParName) - возвращает номер аргумента по имени параметра

  • c :ArgType(iArgNo) - возвращает тип данных указанного аргумента

  • u :ArgValue(iArgNo) - возвращает значение указанного аргумента

  • u :[:...]Default([uPar1[,...]]) - вызывает одну из родительских процедур

  • u :Call([uPar1[,...]]) рекурсивный вызов данной процедуры

  • i :Level - возвращает текущий уровень переопределения процедуры

  • o :Source - возвращает ссылку на объект-источник события

  • u :Value - ссылка на значение свойства (используется только в процедурах обработки событий свойства :access() или :assign())

Эти элементы доступны из любой процедуры:

  • c :BoundTo - путь до привязанного события

  • c :Inherited - имя наследуемого класса

  • c :ParList - список параметров

  • o :Parameters - возвращает коллекцию параметров

  • c :Status - статус процедуры

  • c :Tip - описание возвращаемого значения

  • c :Comment - любые комментарии

  • c :Name - имя процедуры

  • c :MemberType - тип процедуры

  • c :SourceText - исходный код процедуры

возвращаемое значение:

  • c :DataType - тип данных возвращаемого значения

  • c :As - список допустимых классов или типов данных

  • c :DefaultType - тип данных по умолчанию для variant

  • u :DefaultValue - возвращаемое значение по умолчанию

признаки:

  • l :Canceled - признак снятой процедура

  • l :Error - признак ошибки компиляции

  • l :Protected - признак запрета переопределения

  • l :Hidden - признак скрытой процедуры

Делегирование событий (:BoundTo)

Свойство :BoundTo содержит путь до привязанного события в другом объекте. Переадресация (делегирование) события - это передача события, возникшего в одном объекте, для обработки в процедуре одноименного события другого объекта.

Делегирование событий особенно эффективно для компонентов. Все вложенные объекты компонента скрыты, в том смысле, что доступ к ним извне компонента закрыт. Поэтому использование единого обработчика событий, определенного непосредственно в самом компоненте, является хорошей идеей. В этом обработчике можно использовать свойство :Source для определения, в каком объекте произошло событие, и предпринять соответствующие действия. Свойство :Source является аналогом свойства .Target объекта event в языке  JavaScript.

Остальные элементы подложки процедуры будут подробно рассмотрены в следующей статье.

Фундаментальные классы и объектные оболочки

Объект, класс, интерфейс объекта, свойство, метод и событие являются фундаментальными понятиями объектного программирования. Для них предусмотрены соответствующие им фундаментальные классы, на основании которых можно получить объектные оболочки для представления указанных выше понятий в виде обычных объектов.

Список фундаментальных классов:

  • object - оболочка для подложки объекта

  • property - оболочка для подложки свойства

  • procedure - оболочка для подложки процедуры

  • members - оболочка для интерфейса объекта

  • instance - оболочка для класса

Объектные оболочки используются средствами IntelliSense, Дизайнером Объектов и многими другими инструментами интерактивной разработки, но могут быть использованы и Вами, например, для получения исчерпывающей системной информации о соответствующих понятиях.

Базовая объектная модель

Имеется набор готовых базовых классов, которые образуют основу объектной модели и на основании которых создаются пользовательские классы. Базовые классы связаны между собой отношениями наследования, т.е. все базовые классы, за исключением класса empty, имеют родительский базовый класс, который будем называть квази-родительским классом, поскольку термин "родительский класс" подразумевает наличие дочерних классов одного и того же с родительским классом базового класса.

Основой базовой модели является базовый класс empty.

Класс empty является квази-родительским классом (.:AsClass) для двух других базовых классов - data и control.

Пустой объект (empty object)

empty = nothing /* пустой объект

data as empty /* объект данных

control as empty /* объект управления

Базовые классы data и control предназначены для разбиения всех базовых классов объектной модели на две принципиально отличающиеся по назначению части. Объекты базового класса data предназначены для обеспечения удобного способа определения, хранения и выборки данных, а объекты базового класса control обеспечивают управление отображением и корректировкой этих данных (определяют интерфейс пользователя). Базовые классы data и control используются не только для классификации остальных базовых классов, но предназначены также для определения на их основе собственных классов пользователя (компонентов).

Таким образом, все объекты делятся на два типа:

  • Объекты данных (Data objects) - это объекты базовых классов data, structure, collection и всех производных от них базовых классов.

  • Объекты управления (Controls) - это объекты всех остальных базовых классов.

Объекты управления можно добавлять только в те объекты (будем называть их контейнерами), которые специально для них предназначены. Таким образом, контейнерами мы будем называть объекты управления, которые могут включать в себя другие объекты управления.

Объекты данных представляют собой агрегаты данных, в то время как свойства представляют скалярные данные. Двойственная природа объектов данных (одновременно и объект и данные) стала основанием для принятия решения приравнять их к свойствам. То есть объекты данных, если их использование не ограничено списком допустимых базовых классов родителей, можно добавлять во все объекты так же, как и свойства, например, добавить в объект textbox , даже, несмотря на то, что textbox не является контейнером!

Ниже приведена упрощенная схема базовых классов. Жирным шрифтом выделены имена базовых классов, которые относятся к теме данной статьи. Схема базовых классов представлена в виде генеалогического дерева, позволяющая лучше понять природу базовых классов и их взаимосвязь.

Объекты данных (data objects)

empty = nothing /* пустой объект

data as empty /* объект данных

библиотеки:

proclib as data /* библиотека процедур (без свойств)

  datatypes as proclib /* системные функции (скрыт)

  udf as proclib /* функции пользователя

  dll as proclib /* API-функции

структуры :

structure as data /* структура (без процедур).

  element as structure /* XML элемент

  xmlschema as element /* XML схема (XSD)

коллекции:

collection as data /* коллекция

  docset as collection /* коллекция документов

  forms as collection /* коллекция форм

  siblings as collection /* коллекция вложенных объектов

  callstack as collection /* стек вызовов

   ...

виртуальная XML RDBMS:

tblschema as data /* схема таблицы (DDL)

  recset as tblschema /* набор записей (DML)

файловая система:

filesystem as data /* файловая система

folder as data /* каталог

drive as data /* диск

file as data /* файл

textstream as data /* поток текста

объектные оболочки:

object as data /* оболочка объекта

property as data /* оболочка свойства

procedure as data /* оболочка процедуры

members as collection /* коллекция элементов объекта

instance as data /* оболочка класса

прочие:

exception as data /* исключение

xmlparser as data /* XML-анализатор

...

components as data /* user data components

Объекты управления (Controls)

empty = nothing /* пустой объект

control as empty /* объект управления

контейнеры:

form as control /* форма

  application a form = my /* приложение

menubar as control /* полоска меню

popup as control /* выпадающее меню

pageframe as control /* страничный блок

page as control /* страница

grid as control /* сетка

buttonbox as control /* набор кнопок

optionbox as control /* набор опций

...

кнопки:

button as control /* кнопка

  checkbox as button /* независимый флажок

  option as button /* зависимый флажок (опция)

 menuitem as button /* пункт меню

ввод и корректировка данных:

textbox as control/* текстовое поле

  stampbox as textbox /* поле штампа

  numberbox as textbox /* числовое поле

 pattern as textbox /* образец для ячейки колонки

spinner as control /* счетчик

editbox as control /* поле редактирования

...

списки:

listbox as control /* список

 combobox as listbox /* выпадающий список

компоненты:

treeview as control /* дерево

scrollbar as control /* полоска прокрутки

progressbar as control /* полоска процесса

...

прочие объекты:

label as control /* метка

image as control/* картинка

shape as control /* рамка

line as control /* линия

...

components as control /* user control components

Состав и назначение базовых классов объектов управления не сильно отличается от аналогичных базовых классов, используемых в других объектно-ориентированных языках. Поэтому в данной статье рассматриваться не будет.

Структура объекта application
Структура объекта application

Вырожденные классы

Среди объектов данных имеется следующие два вырожденных класса:

  • proclib - элементы объектов созданных на основании этого базового класса могут быть только процедурами (нет свойств, и, как следствие, нет вложенных объектов). Объекты указанного базового класса используются для определения библиотек процедур, и является квази-родительским классом для всех библиотек процедур (datatypes, udf и dll).

  • structure - элементы объектов созданных на основании этого базового класса могут быть только свойствами (нет процедур). Объекты этого базового класса используется для хранения только данных и является квази-родительским классом для element, который, в свою очередь, является квази-родительским классом для xmlschema.

Системные функции

Все системные функции являются методами базового класса datatypes. В языке ULCA, в отличие, например, от языка JavaScript, нет своих базовых классов для отдельных типов данных, т.е. нет явного разделения функций по типам используемых данных. Для группировки функций по типам используемых данных в языке ULCA используются пространства имен. Причем, для группировки функций используется два критерия:

  • тип данных основного параметра функции (как правило, это первый параметр)

  • тип данных возвращаемого значения

В качестве имени пространства используется одна прописная буква латинского алфавита. Это имя ставится перед именем функции и отделяется от него точкой.

Список пространств имен для группировки по типу данных основного параметра:

  • m. операции с числовыми данными

  • c. операции со строками

  • h. операции над хронологическими данными

  • a. операции над массивами

  • b. операции с битовым представлением строк и чисел

  • u. операции с variant-данными

  • o. операции с объектами (создание и обращение)

Каждая функция входит в состав только одного или, в случае наличия нескольких допустимых типов данных для основного параметра функции, в состав нескольких из указанных выше пространства имен.

 Список пространств имен для группировки по типу возвращаемого значения:

  • c. - строка символов

  • n. - вещественное число

  • i. - целое число

  • l. - логическое значение

  • d. - дата

  • t. - время

  • s. - штамп (дата и время)

  • o. - ссылка на объект

  • u. - неопределенное значение

Например:

m.abs() /* эквивалентно math.abs() на языке JavaScript

Имя пространства имен для функций указывать не обязательно, поскольку имена функций являются глобальными. Пространство имен используется средствами IntelliSense. Если Вы поставили почку после имени пространства, то появится список всех имен, включенных в это пространство, из которого можно выбрать нужное вам имя функции. Объект базового класса datatypes скрыт, поскольку добавлять и переопределять системные функции запрещено.

Функции, определенные пользователем

Язык ULCA позволяет создавать собственные функции (User Defined Function) и размещать их в библиотеке my.UDF в виде обыкновенных методов. Имена этих функций не должны совпадать с именами системных функций, поскольку они вызываются на выполнение также как и системные функции (без указателя родителя). Для обращения к ним доступно только одно пространство имен (f.).

Особенностью этих функций является отсутствие у них собственного контента выполнения. Внутри этих функций можно использовать ссылку на контент (This), но она будет указывать не на библиотеку, в которой эта функция размещена, а на объект метода, из которого она была вызвана.

Библиотеку UDF можно использовать для расширения списка системных функций или для хранения функций обратного вызова.

Коллекции

Коллекция в объектно-ориентированных языках, также как и массив, имеет очень неоднозначное толкование. Например, в языке c# под коллекцией понимаются все перечисляемые типы данных (списки, очереди, стеки, наборы, словари и даже массивы).

В языке ULCA все предельно просто: коллекция  - это объект, содержащий ассоциативный массив ссылок на объекты.

Коллекции делятся на два типа, в зависимости от их предназначения:

  • Коллекция ссылок - содержит ссылки на привязанные объекты, которые уже созданы и привязаны к родительскому объекту. Коллекция это типа предназначена для группировки объектов по какому-либо признаку. Примером коллекции ссылок является коллекция дочерних элементов объекта (siblings)

  • Коллекция объектов - содержит ссылки на свободные объекты, доступ к которым возможен только с помощью метода коллекции .Item(). Поскольку свободные объекты - это, в основном, объекты данных, то данный тип коллекции можно назвать коллекцией данных. Примером коллекции данных является коллекция документов (docset).

При удалении элемента из коллекции ссылок, удалятся только ссылка на объект, в то время как в коллекции объектов вместе ссылкой удаляется и сам объект.

Все виды коллекций имеют одинаковый набор основных элементов:

  • o Add(cClass|oObject[,cKey]) - добавить новый объект или ссылку

  • i Index(cKey) - получить значение ключа по заданному индексу

  • c Key(iIndex) - получить значение индекса по заданному ключу

  • o Item(cKey|iIndex) - выбор объекта коллекции по ключу или индексу

  • i Count - размер коллекции

  • l Remove(cKey|iIndex) - удалить элемент коллекции

  • l CaseSensitive - признак зависимости ключа от регистра

Структуры

Понятие структура практически не используется в современных языках программирования. Оно имеет значение в языке C, еще остается в языке C++, а в языке C# уже пропало. Наверное, считается, что нет необходимости выделять структуру в отдельный вид объекта.

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

Элемент  

Объект element - это объект данных, который может быть добавлен в объект практически любого класса (визуального или не визуального). В объект element можно добавлять вложенные объекты класса element или docset.

Объект element был создан для обеспечения совместимости объектной модели, используемой языком ULCA, c другими моделями данных.

В клиентском приложении, для разработки которого и предназначен язык ULCA, особый интерес представляет реляционная модель данных и модель данных, используемая в языках разметки (XML, HTML, JSON и все их модификации)

Сначала рассмотрим языки разметки.

Название базового класса element позаимствовано из XML модели. Свойства объекта element называются атрибутами, что также заимствовано из этой же модели.

Приведем список допустимых значений элемента :MemberType из подложки свойства, который определяет тип всех элементов интерфейса объекта:

стандартные типы:

  • P - Property (ссылка на значение свойства)

  • C - Control (ссылка на вложенный объект управления)

  • D - Data Object (ссылка на вложенный объект данных)

расширение для элементов и атрибутов:

  • X - XML Object (ссылка на вложенный элемент)

  • A - Attribute (ссылка на значение атрибута)

  • T - Text (ссылка на текстовый атрибут)

  • L - Link (ссылка на элемент)

Объект element сильно отличается от всех остальных объектов данных, поэтому ему выделено отдельное значение типа (X)

Корневой тэг XML-документа и все вложенные в него тэги представлены объектами базового класса element. Данные хранятся в атрибутах соответствующего им элемента.

Для совместимости с XML элементом в подложку объекта element были добавленные следующие фундаментальные элементы:

  • .:Text - Текст (значение) элемента

  • .:Offset - Позиция элемента в тексте родительского элемента

  • .:Key - Уникальный ключ элемента в коллекции документов

  • .:XMLNS - Имя пространства имен

  • .:XMLText([iLevel]) - Возвращает XML-текст для данного объекта и всех вложенных в него элементов.

Свойства .:Text и .:Offset предназначены для того, чтобы иметь возможность восстановить исходный вид XML документа из его объектного образа. Свойство .:Key имеет непустое значение, если элемент включен в коллекцию документов.

Одни и те же данные XML-документа могут быть представлены либо в виде атрибута, либо в виде элемента, например: Text = "Привет!" или <Text>Привет!</Text>. С точки зрения объектной модели никакой разницы между этими типами нет - все они являются свойствами. Однако атрибуты в XML-документе должны быть размещены сразу после открывающего тэга и могут следовать в любом порядке, а порядок элементов строго определен и все они должны следовать после атрибутов. В связи с этим, атрибуты объекта element пришлось разделить на два типа: A- Attribute и T - Text, в зависимости от того, как они представлены в исходном XML документе.

У объекта element имеется свойство-ссылка (тип L - Link), которое не относится к атрибутам, и непосредственно не связанный с хранением данных XML-документа. Оно предназначено для ссылок из данного документа на другие документы или элементы этих документов. В XML модели аналогом этого свойства-ссылки является пустой элемент (<Element></Element>). Пустой элемент используется для того, чтобы зарезервировать имя элемента для будущего определения и использования.

Объект класса docset является коллекцией элементов и может быть добавлен только в объект класса element

В языке XML, в отличие от JSON, нет такова понятия, как массив или коллекция. Однако этот язык позволяет добавлять несколько тэгов с одним и тем же именем в один и тот же родительский тэг, что с точки зрения объектной модели недопустимо (имя любого элемента объекта должно быть уникальным в рамках одного объекта). Поэтому все объекты element с одинаковыми именами при парсинге исходного XML документа помещаются в коллекцию элементов.

Таким образом, объектная модель, используемая в языке ULCA, благодаря наличию базовых классов element и docset, оказалась полностью совместимой с моделью данных языков разметки.

Посмотрим внимательно на природу объекта element с точки зрения понятий языка XML. Объект element хранит данные в виде значений атрибутов документа (документ XML) и определения этих атрибутов в их подложках (схема документа XSD). Два в одном! То есть элемент самодостаточен.

Теперь обратимся к реляционной модели данных, а именно к записи таблицы, которая представляет собой линейный список полей. В языке ULCA для объектного представления записи таблицы используется все тот же объект element, в котором поля записи представлены атрибутами. Никаких дополнительных средств для обеспечения совместимости по типам данных и структуре не требуется.

А теперь зададимся вопросом, можно ли сделать совместимым такие, казалось бы, несовместимые понятия, как линейный список полей и структура произвольного уровня вложенности? Есть два крайних подхода, образно сформулированных великими мастерами пера и теоретиком, с большим практическим опытом.

Антон Павлович Чехов и Александр Сергеевич Пушкин, не сговариваясь, ответили категорично:

Нельзя запрячь в одну телегу, коня и трепетную лань!

Основатель дедуктивного метода, с такой же уверенностью сказал:

Элементарно, мой друг Ватсон!

Ну, а если серьезно, то нас интересует только один вид совместимости - совместимость по данным. Значения атрибутов (конечных узлов) структуры представляют собой линейный список, а список полей таблицы - это лишь частный случай структуры (одноуровневая структура). В этом смысле, никакой несовместимости между списками и структурами нет.

Этот вывод имеет очень большое практическое значение. Оказывается, что для хранения данных самых сложных XML документов, можно использовать только поля таблицы и нет необходимости в реляционной базе данных хранить исходный текст XML документа, да еще и его XML схему. Эти данные не используются на стороне Сервера - они нужны на стороне Клиента!

Есть, правда, один момент, связанный с тем, что количество полей в записи таблицы, как правило, ограничено. Эта проблема решается просто и очень эффективно. Но об этом Вы узнаете из статей, посвященных виртуальной XML RDBMS.

Для привязки атрибута к соответствующему ему физическому полю используется свойство :Field подложки атрибута.

Подробную информацию о базовых классах element и docset можно получить из следующих статей, посвященных двум указанных выше моделям данных.

Фундаментальные классы и объектные оболочки

Объект, класс, интерфейс объекта, свойство, метод и событие являются фундаментальными понятиями объектного программирования. Для них предусмотрены соответствующие им фундаментальные классы, на основании которых можно получить объектные оболочки для представления указанных выше понятий в виде обычных объектов и свойств.

Список фундаментальных классов:

  • object - оболочка для подложки объекта

  • members - оболочка для интерфейса объекта

  • property - оболочка для подложки свойства

  • procedure - оболочка для подложки процедуры

  • instance - оболочка для класса

Объекты класса object, property и procedure кроме элементов подложек имеют свойства и процедуры, не присутствующие в подложках, но необходимые для Дизайнера Объектов и других инструментов интерактивной разработки.

Объект класса members представляет собой коллекцию ссылок на объекты класса property и procedure, представляющие интерфейс выбранного объекта.

Объектные оболочки используются средствами IntelliSense, Отладчиком, Дизайнером Объектов и многими другими инструментами интерактивной разработки, но могут быть использованы и Вами, например, для получения исчерпывающей системной информации о соответствующих понятиях.

Объект Instance

Объект instance предназначен для создания "образа" объекта указанного класса.

Для создания этого образа используется функция o.instance(cClass), возвращающая ссылку на специальный объект данных, который, не являясь реальным объектом указанного класса, полностью повторяет его объектную структуру, свойства и процедуры. Для того чтобы отличить полученный объект и все вложенные в него объекты от реальных объектов, нужно проанализировать значение переключателя .:Instance в подложке этих объектов. В реальных объектах этот признак сброшен (равен false)

В определения класса, хранящегося в библиотеке классов, представлены только новые элементы объекта, созданные в этом классе и все переопределенные в данном классе элементы родительских классов. Такой способ представления удобен для хранения определения класса, но не удобен для получения полного представления о нем. Поэтому объект instance наиболее полно представляет выбранный класс (в уже собранном виде).

Объект instance активно используется средствами IntelliSense для объектного представления еще не созданных объектов указанного класса.

Поскольку Приложение, написанное на языке ULCA, представляет собой просто набор классов, то объект Instance, содержащий исчерпывающие данные о выбранном классе (в уже собранном виде), является идеальным объектным инструментом, достаточным для создания web версии Приложения.

Компоненты

Понятие "компонент" активно используется при разработке web приложений, но почему-то очень редко встречается в desktop приложениях.

Компонент - это контейнер, имеющий вложенные в него скрытые объекты, доступ к которым извне закрыт. Это прекрасный инструмент для разработки пользовательских "простых" классов, используемый для создания, как визуальных объектов, так и объектов данных.

Идеальными базовыми классами для разработки компонентов являются базовый класс control (для визуальных компонентов) и базовый класс data (для компонентов данных). Для закрытия доступа ко всем вложенным объектам компонента нужно использовать доступное для чтения и записи свойство-переключатель подложки объектов .:Hidden.

P.S.

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

Теги:
Хабы:
-6
Комментарии13

Публикации

Истории

Ближайшие события

4 – 5 апреля
Геймтон «DatsCity»
Онлайн
8 апреля
Конференция TEAMLY WORK MANAGEMENT 2025
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань
20 – 22 июня
Летняя айти-тусовка Summer Merge
Ульяновская область