Введение
Причины введения поддержки ООП в языке Кларион
Реализация ООП в CW 2.1
Основные различия между Кларионом и Объектным Паскалем
Заключение

Листинг 1. Простейший пример описания классов на Кларионе
Листинг 2. Пример реализации методов
Листинг 3. Пример задания метода через процедурный тип
Листинг 4. Автоматическое разыменование объектов
Листинг 5. Абстрактный класс
Листинг 6. Работа со свойствами


Введение

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

С чем же связан такой ход фирмы TopSpeed, ныне отвечающей за развитие программного инструментария семейства Clarion, и что хорошего сулит ООП в трактовке языка Кларион для всех, кто так или иначе связан с разработкой на его основе прикладных систем?

Объектная модель, введенная в язык Кларион, заметно отличается от моделей, принятых в других языках. Чтобы разобраться в ее особенностях, имеет смысл сравнить подход Клариона с тем, что принят в Объектном Паскале (Object Pascal)1 - прямом преемнике такого известного диалекта классического Паскаля, как Borland Pascal 7.0. Ведь именно Объектный Паскаль является фундаментом среды программирования Delphi 1.0 и 2.02 - одного из основных конкурентов инструментальной среды Clarion for Windows на рынке средств визуальной разработки информационных систем для Windows-платформ.

Классы присутствуют в Кларионе начиная с версии Clarion for Windows 2.0, выпущенной в июле 1996 г. В версии Clarion for Windows 2.1 (CW 2.1), появившейся в начале этого года, в реализацию ООП внесены некоторые коррективы; в данной статье мы будем рассматривать именно эту, более новую версию. Объектный Паскаль рассматривается в варианте Delphi 2.0.

Причины введения поддержки ООП в языке Кларион

Идеолог языка Кларион - Брюс Баррингтон долго придерживался весьма своеобразного взгляда на ООП3, считая возможным обойтись так называемыми ненаследуемыми классами (WINDOW, FILE, REPORT, VIEW и т. п.) - а по сути предопределенными абстрактными типами данных. Что же послужило причиной резкого изменения его точки зрения? Почему фирма TopSpeed все-таки приняла решение вводить в языке Кларион поддержку ООП?

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

Главное, что отличает Кларион от Объектного Паскаля в интересующем нас аспекте, - это роль, отводимая классам и объектам. Если в Объектном Паскале классы служат основными строительными блоками (что очень наглядно демонстрирует система Delphi), то в Кларионе они не более чем еще одно вспомогательное средство, без которого можно безболезненно обойтись (кстати, в программных шаблонах, определяющих основные подручные средства и материалы для разработчика, классы стали использоваться только с версии CW 2.1).

Как и в любом масштабном деле, при создании Clarion-технологии (а основы ее закладывались более 10 лет назад) были выбраны вполне определенные ориентиры. Среди языков это были Кобол, Бейсик, Си и Паскаль (пожалуй, именно в таком порядке); в качестве оказавших наибольшее влияние на Clarion сред разработки для Windows следует назвать Delphi, Visual Basic и PowerBuilder.

До появления CW 2.0 язык Кларион поддерживал четыре парадигмы программирования:

  • процедурную (процедуры, статическая типизация);
  • событийную (ACCEPT-цикл, экспресс-клавиши, события);
  • родовую (родовые типы данных, расширение конструкций GROUP и QUEUE);
  • сущностную (ненаследуемые классы, property-сообщения).
  • Все это обусловило гибридный характер языка. Слово "гибридный" здесь не негативная оценка, а констатация факта. Гибридными являются подавляющее большинство существующих ныне языков. "Чистых" не так уж и много: из наиболее известных - классический Паскаль (процедурное программирование), Лисп (функциональное программирование), Пролог (логическое), Смолток (объектно-ориентированное), SETL (работа с множествами).

    Кстати сказать, событийная модель, поддерживаемая в языке Кларион, создает весьма удобный абстрактный слой, который редко встречается в языках программирования. Родовая модель (generic programming) в стиле Никлауса Вирта обеспечивает унифицированную работу с разнородными структурами данных. Ну а сущностная парадигма - просто "визитная карточка" Клариона. Наиболее важный ее недостаток - невозможность наследования свойств объектов - может быть устранен только за счет ООП.

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

    Реализация ООП в CW 2.1

    Если быть кратким, то для поддержки ООП в язык Кларион добавлены четыре элемента:

  • новый конструктор типа данных с именем CLASS, во многом аналогичный конструктору GROUP;
  • методы (процедуры и функции, интерфейс которых задается не в MAP-разделе, а внутри описания CLASS);
  • возможность заведения переменной-ссылки на класс;
  • операторы NEW и DISPOSE, обеспечивающие соответственно динамическое создание и удаление объектов.
  • Побочным следствием реализации ООП в Кларионе стала возможность определять внутри одной программы несколько функций, носящих одинаковое имя, но использующих разный набор параметров. Скажем, Inc ( *LONG x ) и Inc ( *LONG x, LONG dx ) - это две разные процедуры, которые компилятор, а впоследствии и компоновщик сумеют различить благодаря особой структуре внутренних имен, учитывающей последовательность параметров и способ их передачи.

    Рассмотрим теперь фрагменты простейшей программы на Кларионе, в которой используется работа с классами.

    В листинге 1 приведено описание несложной иерархии из четырех классов. Класс Figure ("геометрическая фигура") обозначает обобщающий (абстрактный) класс. Класс Quadro ("четырехугольник") определяет объекты, имеющие поля (p1, p2, p3, p4), которые задают координаты углов четырехугольника. Для работы с объектом данного класса предназначен метод Perimeter ("периметр"). Класс Rectangle ("прямоугольник") создается на основе класса Quadro; обратите внимание: в круглых скобках после слова CLASS в описании Rectangle фигурирует имя Quadro. Но в описании класса Rectangle переопределяется метод Perimeter базового класса (Quadro). И понятно почему: периметр прямоугольника можно вычислить по более простой формуле, чем периметр произвольного четырехугольника. Наконец, по аналогии на основе класса Rectangle строится класс Square ("квадрат"). Реализация методов (см. листинг 2) отличается от реализации обычных процедур тремя особенностями:

  • имя метода указывается полностью, с обозначением его принадлежности тому или иному классу;
  • внутри метода всегда определена специальная переменная с именем SELF; ее можно рассматривать как скрытый первый параметр, служащий для передачи имени объекта, к которому относится данный метод;
  • по аналогии с SELF внутри метода можно использовать ключевое слово PARENT, которым обозначается реализация метода в родительском (базовом) классе.
  • Ключевое слово PARENT весьма удобно в тех случаях, когда метод вновь порождаемого класса реализуется по аналогии с родительским, но с небольшими его изменениями. Например, чтобы обеспечить "звуковую" трассировку работы метода Square.Perimeter, можно написать:

    Square.Perimeter FUNCTION
            CODE
                    BEEP(BEEP:SystemDefault) 
                    ! звуковой сигнал
                    RETURN PARENT.Perimeter() 
                    ! вызывается Rectangle.Perimeter

    Заметьте, что описания классов не содержат атрибута TYPE. В соответствии с правилами Клариона это означает, что создается не только тип, но одновременно и объект данного класса; таким образом, объекты можно создавать статически (в том числе используя ключевое слово LIKE). Однако на практике чаще требуется порождать их динамически. Для этого нужно описать переменную-ссылку на класс и явным образом запросить под нее память из динамической области (кучи). Вот как это выглядит на Кларионе:

    q &Quadro
      CODE
      ...
      q &= NEW Quadro

    Виртуальные методы (обозначаемые ключевым словом VIRTUAL) - это дань традиции, берущей свое начало от Си++. Как известно, виртуальные методы базируются на понятии динамического типа данных. Напомню, что в языках со статической типизацией, таких как Паскаль, Си, Модула-2, тип данных однозначно определяется на этапе компиляции. В языках с динамической типизацией (например, в Смолтоке) переменные объявляются без указания типа. В гибридных языках - Кларионе, Объектном Паскале, Си++ - объекты имеют как статический тип (задаваемый при объявлении), так и динамический: если объект некоторого класса подставляется (в выражении, при передаче параметров, при вызове метода) вместо объекта из класса, стоящего ниже в иерархии, то его динамический тип изменяется. Динамический тип используется прежде всего для того, чтобы обеспечить выбор на этапе выполнения методов, которые должны быть вызваны в данной точке программного кода. Рассмотрим пример:

    Quadro = Rectangle
    s = Quadro.Perimeter()

    Первое присваивание вполне допустимо: более конкретный класс объекта Rectangle проецируется на менее конкретный класс Quadro. (Обратное присваивание было бы некорректным, поскольку проекция менее конкретного объекта на более конкретный не всегда однозначно определена.) После присваивания динамический тип переменной Quadro уже не тождественен статическому - это тип Rectangle. Второе присваивание, в котором производится вычисление периметра, вызывает естественный вопрос о том, какой метод будет здесь вызван: Quadro.Perimeter или Rectangle.Perimeter? Вот тут-то и вступают в дело виртуальные методы. Если метод Perimeter в базовом и в его производных классах задать с атрибутом VIRTUAL, то вызов осуществляется в соответствии с динамическим типом объекта, т. е. вызывается метод Rectangle.Perimeter. Если же этот атрибут опустить, динамический тип не "срабатывает" и вызывается метод Quadro.Perimeter.

    Очевидный недостаток подобной "виртуализации" (с которым давно уже пытаются бороться в Си++) состоит в том, что полиморфный метод должен быть объявлен с атрибутом VIRTUAL во всех классах, где он определяется. Таким образом, оказывается нарушен основной принцип наследования: если при проектировании классов вы забыли вверху иерархии предусмотреть "виртуализацию" данного метода, то придется вносить правку по всей цепочке, т. е. переделывать ранее определенные классы. Многие решают эту проблему просто - "на всякий случай" определяют большинство методов как виртуальные, и в результате таблица виртуальных методов (Virtual Methods Table, VMT) засоряется лишними точками входа.

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

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

    Остается добавить, что при объявлении класса в языке Кларион разрешается указывать атрибуты, которые влияют на физическое представление объектов (EXTERNAL, DLL, STATIC, BINDABLE, THREAD). Наиболее важным из них, на мой взгляд, является атрибут THREAD, означающий, что объекту будет выделяться особая область памяти для каждого исполняемого потока (thread).

    Основные различия между Кларионом и Объектным Паскалем

    В контексте данной статьи мы ограничим сравнение только теми элементами двух языков, которые имеют непосредственное отношение к ООП.

    Методы класса

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

    Конструкторы и деструкторы

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

    Альтернативное решение проблемы - реализовать конструктор как специальную процедуру (функцию), которая и будет методом собственно класса, а не объекта. Что же касается деструкторов, а также конструкторов, инициализирующих уже порожденные объекты, то они могут быть реализованы просто как обычные методы объекта.

    Методы обработки сообщений

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

    T = class
    ...
    procedure WMSize (var msg: TWMSize); message WM_Size;
    end;

    В языке Кларион обработка событий строится не на основе классов. Более того, исполняющая система (RunTime System, RTS) Клариона сама фильтрует и преобразует низкоуровневые и высокоуровневые события Windows, так что здесь нет взаимно-однозначного соответствия между событиями языка и событиями операционной системы (ОС). Кстати, это один из серьезных плюсов языка, поскольку так обеспечивается его независимость от используемой ОС.

    Указатели на методы

    Объектный Паскаль допускает в объектах поля процедурного типа. Основное их назначение - слегка замедлить рост иерархии классов. Этот прием создает "горизонтальные" связи между классами и в Delphi чаще всего используется для обработки событий. Прямого аналога в Кларионе он не имеет, поскольку там переменные процедурного типа разрешены только в качестве параметров процедур и функций, а в полях данных находиться не могут. Тем не менее в Кларионе несложно задать переменную-метод в поле объекта с помощью того же процедурного типа. Посмотрите на пример в листинге 3, где через процедурный тип задается метод pr объекта MyClass.

    Статические и динамические объекты

    Автоматическое разыменование объектов, которое относительно недавно введено в Объектный Паскаль, используется и в языке Кларион. Другими словами, работая со ссылками на объекты, можно для удобства записи опускать знак разыменования при обращении к полям и методам: вместо p^.x записывают просто p.x. В Кларионе объекты разрешается создавать и в статической, и в динамической памяти, в Объектном Паскале - только в динамической (иначе говоря, работа всегда происходит со ссылками на объекты). Взгляните на листинг 4.

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

    Абстрактные классы

    При создании иерархии классов часто требуется определить базовый класс, содержащий только фиктивные (абстрактные) методы. Простейший пример - класс Figure (геометрическая фигура) с методом Area, позволяющим вычислить площадь фигуры (см. листинг 5). Понятно, что этот метод невозможно определить в классе Figure, поскольку его реализация для разных фигур должна быть разной: для прямоугольника формула вычисления площади одна, для треугольника другая, для круга - третья и т. д. Подобные абстрактные методы в Объектном Паскале задаются с помощью ключевого слова abstract, а в языке Кларион используется просто фиктивная реализация. При взгляде на пример из Объектного Паскаля сразу же возникает неясность. Что такое virtual без abstract - понятно, а вот что такое abstract без virtual?

    Свойства

    Следующим заметным отличием Объектного Паскаля является понятие свойства (property). Это не что иное, как новый абстрактный слой, позволяющий синтаксически иным, более удобным способом обозначать доступ к внутренним полям объекта. Компилятор языка "разворачивает" синтаксис свойства в вызов указанных программистом методов. Следует заметить, что в языке Кларион также имеется схожий механизм свойств, но работает он только для специальных ненаследуемых классов - WINDOW, FILE, REPORT и т. п. (см. листинг 6).

    Особенно активную роль в языке Кларион свойства играют при работе с OCX-компонентами:

    IF CurrentEvent <> OcxEVENT:MouseMove
    s = 'Event:' & OleControl{PROP:LastEventName}
    END

    Заметим, что в версии CW 2.1 компонентному программированию уделяется гораздо больше внимания, чем ООП.

    Разделы описания классов

    Поля и методы в описании класса в Объектном Паскале, в отличие от Клариона, распределяются по четырем разделам: public, private, protected и published. Раздел задает степень доступности (видимости) полей и методов. Поля и методы, объявленные в разделе public, доступны для всех клиентов данного класса, объявленные в разделе private представляют собой "секретную" информацию, не подлежащую разглашению. Раздел protected содержит своего рода информацию "для служебного пользования", доступ к которой возможен лишь при соблюдении специальных условий. Что касается раздела published, то его роль представляется несколько надуманной: он отличается тем, что объявленные в нем поля и методы видны инспектору объектов (Object Inspector), и в нем же должны объявляться свойства объекта (см. выше).

    С одной стороны, такой подход выглядит привлекательнее, чем подход языка Кларион, в котором имеется только атрибут PRIVATE, объявляющий "приватным" соответствующее поле данных или же метод. Однако признака разграничения прав доступа по чтению-записи для методов и полей, подобного тому, что используется в языке Оберон-24, нет ни в Кларионе, ни в Объектном Паскале. (Справедливости ради отмечу, что в Объектном Паскале требуемого эффекта все-таки можно добиться с помощью все тех же свойств.) Схему Объектного Паскаля (кстати, во многом схожую с принятой в языке Java) вряд ли можно рассматривать как пример для подражания, особенно учитывая, что существует более простое и элегантное решение. Оно реализовано в языке Модула-3, созданном в Центре системных исследований фирмы Digital (DEC SRC), и состоит в том, что классы заключаются внутрь модулей, имеющих не один, а множество различных интерфейсов. Каждый интерфейс отражает свой "взгляд" на содержимое модуля. В свете недавно разработанной в исследовательском центре PARC фирмы Xerox новой концепции аспектно-ориентированного программирования (Aspect-Oriented Programming) такой подход выглядит куда более гибким и перспективным.

    Динамическая проверка типа

    Еще более существенным отличием Объектного Паскаля от Клариона является возможность проверки и изменения типа объектов в процессе выполнения программы. В Объектном Паскале имеются два встроенных оператора: is и as. Первый отвечает за проверку динамического типа объекта, второй - за приведение динамического типа к требуемому.

    IF CurrentEvent <> OcxEVENT:MouseMove
    s = 'Event:' & OleControl{PROP:LastEventName}
    END

    Динамическая проверка, опирающаяся на оперативную информацию о типе (RunTime Type Information, RTTI), крайне важна для надежной работы объектной схемы. Если у программиста нет средств, позволяющих в явной форме запрашивать и модифицировать динамический тип объекта, то возникает вероятность путаницы и множество других проблем. Правда, для CW 2.1 определение динамического типа и приведение его к требуемому вполне могут быть реализованы через небольшую библиотеку (которую автор статьи написал и отладил за пару дней). Однако во избежание будущих возможных побочных эффектов столь важный механизм, конечно, должен все же поддерживаться самой фирмой TopSpeed, а не внешними разработчиками.

    Обработка исключительных ситуаций

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

    Обработка исключительных ситуаций строится на двух парах блоков программного кода: try-except и try-finally. Распознавание их производится в блоке except с помощью оператора on-do, возбуждение и дальнейшее распространение исключений в охватывающий блок - с помощью параметризуемого оператора raise. Эта схема достаточно проста и охватывает широкий диапазон различных вариантов обработки исключительных ситуаций. В то же время решение сделать исключительные ситуации не встроенным типом данных, а расширяемым классом приводит к чересчур гибкой трактовке. Дело в том, что исключительные ситуации по самой своей природе подразумевают не отложенную, а немедленную обработку. Они быстро возникают и быстро исчезают; в этом смысле их представление в форме объектов выглядит несколько искусственным. Реализация исключительных ситуаций через класс Exception не совсем удачна еще и потому, что для эффективной работы накладные расходы на их поддержку должны быть сведены к минимуму.

    Практика использования исключительных ситуаций и опыт работы с ними в различных языках программирования (Ада, Эйфель, Модула-2, Модула-3, CLU и др.) показали, что вполне достаточно идентифицировать исключительную ситуацию парой "источник-номер", где в роли источника выступает, в зависимости от замысла разработчика, либо модуль, либо класс. Хотя обработка исключительных ситуаций и не связана напрямую с ООП, отсутствие ее в языке Кларион должно считаться существенным недостатком. Здесь фирме TopSpeed, на мой взгляд, есть над чем работать. Во всяком случае с момента приезда Брюса Баррингтона в Москву и обсуждения с ним этого вопроса прошел почти год, а средства обработки исключительных ситуаций в Кларионе пока не появились. По всей видимости, автору статьи так и не удалось убедить идеолога Клариона в настоятельной необходимости соответствующего механизма.

    Заключение

    Какие же выводы можно сделать на основе проведенного сравнения? Схема, предлагаемая Объектным Паскалем, представляется весьма изощренной и запутанной. Это естественное следствие тотальной, если можно так выразиться, "объектизации" системы Delphi и всего того, что предполагается создавать с помощью Объектного Паскаля. Многие встроенные возможности языка могут быть реализованы куда более простым путем. А ведь обилие возможностей - это не только удобство и продуктивность работы, но и повод для лишней головной боли, и дополнительные, часто неоправданные затруднения. Кларион использует на порядок более простую и лаконичную объектную схему, во многом напоминающую ту, которую фирма TopSpeed ввела в разработанный ею диалект языка Модула-2, а также (с небольшими, хотя и важными отличиями) схему, примененную в языке Оберон-2. Однако объектная схема Клариона не полностью сбалансирована, и отсутствие в ней ряда дополнительных возможностей (таких, как проверка динамического типа, контроль чтения-записи) ограничивает ее применение.

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

    В Объектном Паскале значительно больше различных механизмов, чем в Кларионе, что роднит его с такими языками, как Си++ и Java. Однако, будучи основой программирования в Delphi, он не имеет никаких средств, которые позволили бы причислить его к языкам четвертого поколения, тогда как Кларион такими средствами обладает. Это особые типы данных (WINDOW, FILE, REPORT и т. п.) и соответствующие им свойства и операторы, а также встроенные средства обработки событий, которые дают возможность не только добиваться лаконичности кода при работе с основными строительными блоками, но и, что особенно важно, выявлять на этапе компиляции серьезные ошибки при использовании этих типов. Извечная проблема гибкости внешних библиотек (процедур и классов) и удобства встроенных типов и операторов в языке Кларион решается за счет вполне разумного баланса, тогда как в Объектном Паскале явно "перевешивают" библиотеки, на которые наводится "косметический лоск" с помощью механизма свойств объектов. И, что вообще характерно для нынешнего этапа развития языков программирования, Объектный Паскаль, как и подавляющее большинство других гибридных языков, делает акцент именно на модном объектно-ориентированном программировании. Кларион же отводит ООП вспомогательную роль, давая зеленый свет нескольким равноправным парадигмам программирования, каждая из которых может быть пущена в дело, когда этого потребуют особенности той или иной задачи.

    К числу серьезных недостатков обоих языков следует отнести закрытость: оба они "узурпированы" соответствующими фирмами-разработчиками - TopSpeed и Borland. Происходящие в результате бесконтрольные изменения при всех их видимых и реальных плюсах вряд ли стоят того, чтобы постоянно поддерживать бедных разработчиков в состоянии "повышенной боевой готовности".

    Несмотря на постепенное введение ООП-элементов в текст своих программных шаблонов, в ближайшее время фирма TopSpeed вряд ли будет усиливать поддержку ООП в Кларионе. Скорее всего, она сосредоточит усилия на более актуальной сейчас проблеме - организации удобной и полноценной поддержки OLE и OCX (ActiveX). Наличие в Кларионе ненаследуемых классов, мощного механизма шаблонов, родовых типов данных, а теперь и поддержки OLE-компонентов несколько затушевывает ценность введения непосредственно в язык элементов ООП - тем более что работа с таблицами БД и пользовательским интерфейсом строится полностью на ненаследуемых классах. Однако обычные классы уже в ближайшем будущем могут стать весьма важным механизмом, выполняющим множество функций: от вспомогательных, таких как поддержка запоминаемых на диске долговременных объектов (persistent objects), которые позволят, к примеру, хранить информацию о состоянии приложения, до создания гибридных объектно-реляционных баз данных.


    Руслан Богатырев - программист, e-mail: ruslan@irdis.msk.su

    Листинг 1. Простейший пример описания классов на Кларионе

    PROGRAM
            MAP
                    Distance ( Point p1, Point p2 ), REAL
            END
    
    Point           GROUP, TYPE
    x                       REAL
    y                       REAL
                    END
    
    Figure  CLASS       ! абстрактный класс для двумерных фигур
    Area                    FUNCTION, REAL, VIRTUAL        ! площадь фигуры
    Perimeter               FUNCTION, REAL, VIRTUAL        ! ее периметр
                    END
    
    Quadro  CLASS(Figure)  ! четырехугольник
    p1                      LIKE(Point)
    p2                      LIKE(Point)
    p3                      LIKE(Point)
    p4                      LIKE(Point)
    Perimeter               FUNCTION, REAL, VIRTUAL ! доопределение Perimeter
                    END
    
    Rectangle       CLASS(Quadro)
    Area                    FUNCTION, REAL, VIRTUAL ! доопределение Area
    Perimeter               FUNCTION, REAL, VIRTUAL ! переопределение Perimeter
                    END
    
    Square  CLASS(Rectangle)
    Perimeter               FUNCTION, REAL, VIRTUAL ! переопределение Perimeter
                    END
    
    
    
    
    
    

    Листинг 2. Пример реализации методов

    Distance        FUNCTION ( Point p1, Point p2 )
            CODE
                      RETURN SQRT((p1.x-p2.x)^2 + (p1.y-p2.y)^2)
    
    Quadro.Perimeter        FUNCTION
            CODE
                    RETURN  Distance(SELF.p1, SELF.p2) + |
                            Distance(SELF.p2, SELF.p3) + |
                            Distance(SELF.p3, SELF.p4) + |
                            Distance(SELF.p4, SELF.p1)
    
    Rectangle.Area  FUNCTION
            CODE
                    RETURN  Distance(SELF.p1,SELF.p2)* |
                            Distance(SELF.p2,SELF.p3)
    
    Rectangle.Perimeter     FUNCTION
            CODE
                    RETURN  (Distance(SELF.p1, SELF.p2) + |
                            Distance(SELF.p2, SELF.p3))*2
    
    Square.Perimeter        FUNCTION
            CODE
                    RETURN  Distance(SELF.p1, SELF.p2)*4
    
    
    
    
    
    

    Листинг 3. Пример задания метода через процедурный тип

    PROGRAM
                    MAP
                            Func (REAL x, REAL y), REAL, TYPE ! процедурный тип
                            Plus (REAL x, REAL y), REAL
                            Minus (REAL x, REAL y), REAL
                    END
    
    a REAL
    
    MyClass CLASS
    pr      FUNCTION (Func f, REAL x, REAL y), REAL
            END
    
    MyClass.pr FUNCTION (Func f, REAL x, REAL y)
            CODE
                    RETURN f(x,y)
    
    Plus    FUNCTION (REAL x, REAL y)
            CODE
                    RETURN x+y
    
    Minus   FUNCTION (REAL x, REAL y)
            CODE
                    RETURN x-y
    
      CODE
            a = MyClass.pr(Plus,100.0,2.0)   ! 100.0 + 2.0
            a = MyClass.pr(Minus,10.0,7.5)   !  10.0 - 7.5
    
    
    
    
    
    

    Листинг 4. Автоматическое разыменование объектов

    Объектный Паскаль       Кларион
    
      type
        MyObject = class              MyObject CLASS, TYPE 
             x: real        x REAL
        end;          END
      var
        p: MyObject;        p MyObject
            q &MyObject
      ...   ...
    
      p.x := 0.1;   p.x = 0.1
            q.x = 0.1
    
    
    
    
    
    

    Листинг 5. Абстрактный класс

    Объектный Паскаль
    
            type
                    Figure = class
                            function Area: real; 
            virtual; abstract;
                            end;
    
    Кларион
    
    Figure  CLASS, TYPE
    Area            FUNCTION, REAL, VIRTUAL
                                    END
    
    Figure.Area FUNCTION
                            CODE
                                    RETURN 0.0
    
    
    
    
    
    

    Листинг 6. Работа со свойствами

    Объектный Паскаль
    
      type
        Window =
          class
            x: integer;
            ...
            function GetWinX : integer;
            procedure SetWinX  (new: integer);
            property WinX: integer read GetWinX write SetWinX;
          end;
    
      var
        w: W;
        t: integer;
    
       w.WinX := 10; {компилятор преобразует в w.SetWinX(10);}
       t := w.WinX;  {компилятор преобразует в t := w.GetWinX;}
    
    Кларион
    
    w    WINDOW('My Window'), AT(5,20,260,100)
         END
    
    t    SHORT
         w{PROP:At,1} = 10 ! меняем координату x окна с 5 на 10
      
         t = w{PROP:At,1}


    1 Матчо Дж., Фолкнер Д. Delphi. М.: "БИНОМ", 1995.
    2 Орлик С. Секреты Delphi на примерах. М.: "БИНОМ", 1996.
    3 Баррингтон Б. Истоки языка Clarion. "Технология программирования", 1995, т. 1, # 1, с. 187-194. Сокращенный перевод предыдущего варианта этой статьи: Брюс Баррингтон. Как создавался Кларион. "Мир ПК", 1993, # 2, c. 56.