CLASS — определение класса в Clarion

Переведен стандартный раздел помощи для ключевого слова «CLASS». Рекомендуется к прочтению.
Описание класса:

label CLASS([parentclass])[,EXTERNAL] [,IMPLEMENTS] [,DLL] [,STATIC] [,THREAD]
           [,BINDABLE] [,MODULE( )] [, LINK( )] [, TYPE]
                     [ data members and methods ]
      END

• label — это имя класса (по аналогии с именем переменной)

• CLASS — ключевое слово, описывающее объект, содержащий data members
(свойства) и methods(методы), выполняющие операции с данными.

• parentclass — имя ранее объявленного родительского класса, чьи свойства и методы наследует данный новый класс.
Родительским классом может быть класс, объявленный с атрибутом TYPE.

• EXTERNAL — говорит о том, что класс описан и память для него выделяется во внешней (external) библиотеке (library).

• IMPLEMENTS — определяет интерфейс (INTERFACE) для класса. Это добавляет дополнительные методы в реализацию класса.

• DLL — говорит о том, что класс объявлен в dll. Атрибут DLL требует наличия атрибута EXTERNAL.

• STATIC — атрибут определяет, что память для свойств класса будет выделяться статически (постоянно).

Cтатические данные выделяются в памяти и освобождаются только по завершению работы программы. Динамические данные
это данные, для которых память выделяется на время их использования, т.е. после использования память освобождается.
Немного подробнее можно прочитать
в разделе помощи Global, Local, Static, and Dynamic.

• THREAD — атрибут говорит о том, что память для переменных будет выделяться отдельно
для каждого запущенного потока. Этот атрибут подразумевает также добавление атрибута STATIC для локальных данных. Атрибут THREAD нельзя использовать
совместно с атрибутом TYPE.

• BINDABLE — определяет, что все переменные класса могут быть использованы в динамических выражениях

• MODULE — определяет модуль-источник кода, содержащий реализацию методов класса. Этот атрибут выполняет
те же функции что и ключевое слово MODULE в MAP-структуре. Если этот атрибут пропущен, то описание
методов класса должно находиться в том же модуле, в котором описан класс.

• LINK — определяет модуль, содержащий описание методов класса, который будет
автоматически добавлен при линковке в проект. Это означает, что вам не нужно !!! добавлять ваш
clw-файл с описаниями методов класса в проект.

• TYPE — говорит о том, что класс является только описанием, а не объектом.

В оригинальном описании для класса используется слово объект. IMHO это несколько неправильно. В принятой ООП-терминологии
объект считается объектом (или экземпляром класса), когда он существует в памяти. В основном же, при описании
класса используется атрибут TYPE, что говорит о том, что объекта нет — есть только его описание. Если проводить аналогии
с простыми типами данных, то: слово LONG — это описание, а MyVar LONG — это объект типа LONG, т.е.
нельзя присвоить LONG=5, а MyVar=5 можно.

• data members and methods — определение данных (свойств) и прототипов процедур (методов).
Свойства могут быть только «простыми» типами данных. Т.е. нельзя использовать внутри класса описание другого класса или очереди («сложные» типы данных).
Но свойства могут быть ссылками на «сложные» типы данных. Для свойств можно использовать операторы
WHAT и WHERE.
Описание класса должно заканчиваться точкой «.» или словом END.

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

Свойства объекта (инкапсуляция)
Каждый экземпляр класса, будь то базовый класс или производный класс
содержит свой собственный набор данных (свойства), принадлежащий экземпляру класса (т.е. если создать два экземпляра класса,
то у каждого экземпляра будет свой набор данных). На методы класса это не распространяется, т.е. существует
только одна копия наследуемых методов (принадлежащих классу, в котором они были описаны), которую
могут использовать любые экземпляры класса и экземпляры производных классов.
Свойства могут быть приватными (private) или публичными (public).
Методы класса, объявленного с атрибутом TYPE не могут быть вызваны напрямую (как ClassName.Method), они могут
быть вызваны только как методы объекта, являющегося экземпляром этого класса (т.е. Object.Method).

Виртуальные методы (полиморфизм)
Если метод определен в родительском классе с атрибутом VIRTUAL, то при
описании его в производном классе, также требуется наличие атрибута VIRTUAL.
Атрибут VIRTUAL в обоих прототипах создает виртуальные методы, что позволяет
методам родительского класса вызывать метод производного класса, имеющий такое же имя как и родительский метод.
Это позволяет расширить функциональность производного (дочернего) класса, о котором родительский
класс не знает ничего.
Виртуальный метод в дочернем классе может вызвать метод с тем же именем
в родительском классе с использованием ключевого слова PARENT впереди имени метода (PARENT.MyMethod).
Это позволяет наращивать наследование, т.е. дочерний метод вначале вызывает родительский метод,
а потом дополняет и расширяет функциональность для нужд дочернего метода.

Область действия (видимости)
Область действия объекта зависит от того где объявлен объект.
В основном объект появляется в области действия после слова «CODE» и покидает область действия
в конце связанных с словом «CODE» секций. Динамически созданный объект (оператор NEW) использует ту
область действия, в которой он создается.
Объект может быть объявлен:

• как глобальные данные — область видимости все приложение;

• как данные модуля — область видимости — модуль;

• как локальные данные — область видимости — процедура
Методы, описанные в дочерних классах, которые в свою очередь описаны
в секции локальных данных процедуры, являются ЛОКАЛЬНЫМИ методами и используют область действия процедуры,
также как и локальные переменные и подпрограммы (routine). Соответственно, код для методов
должен быть описан в том же модуле-источнике (*.clw), где и описан код процедуры и определяемого в ней класса.
Код методов должен начинаться после всех подпрограмм (routine) процедуры, и перед следующим кодом другой
процедуры (если таковая есть в этом же модуле). Это означает, что локальные переменные и
подпрограммы могут быть использованы в методах локального класса. Пример:

SomeProc    PROCEDURE
MyLocalVar     LONG
MyDerivedClass CLASS(MyClass)      ! дочерний класс с виртуальным методом
MyProc            PROCEDURE,VIRTUAL
               END
 CODE
 ! здесь начинается главный исполняемый код процедуры SomeProc
 ! здесь начинаются подпрограммы для процедуры SomeProc
MyRoutine ROUTINE
 ! здесь код подпрограммы
 ! описание методов класса MyDerivedClass начинается сразу после подпрограмм
MyDerivedClass.MyProc PROCEDURE
 CODE
 MyLocalVar = 10 ! MyLocalVar находится в области видимости и ее можно использовать в этом месте
 DO MyRoutine    ! MyRoutine также в области видимости, и подпрограмму можно использовать в коде метода

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

Объявление объекта
Вы можете описать объект простым присваиванием имени объекта к слову CLASS, точно
также как присваивание переменной типа данных. Также вы можете выполнить оператор NEW для создания класса, в этом случае
класс должен быть описан как ссылка на уже существующий класс. Еще один путь создания класса — это
наследование свойств и методов другого класса для данного объекта. При определении объекта можно пользоваться
всеми атрибутами слова CLASS, за исключением атрибутов MODULE и TYPE.
Когда атрибут TYPE отсутствует в описании класса, то выполняются сразу две вещи: описание класса и создание объекта класса.
Если атрибут TYPE присутствует, то объект класса не создается.

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

MyClass  CLASS ! описание класса + создание объекта
MyField     LONG      ! свойство
MyProc      PROCEDURE ! метод
         END

А здесь только описание класса, объекта НЕТ (т.е. нельзя сказать MyClass.MyField=10):

MyClass  CLASS,TYPE
MyField     LONG
MyProc      PROCEDURE
         END

Предпочтительнее использовать прямое описание класса, нежели использование
ссылки на класс. При прямом описании несколько меньше кода по написанию и не требуется использование
операторов NEW и DISPOSE для создания и уничтожения объекта. Преимущество использования NEW/DISPOSE
состоит в контроле за временем жизни объекта. Пример:

MyClass  CLASS,TYPE
MyField     LONG
MyProc      PROCEDURE
         END

OneClass MyClass           ! прямое описание объекта, "мало кода" :)

TwoClass &MyClass          ! ссылка на класс, нужно использовать NEW/DISPOSE

 CODE
 ! здесь выполняется какой то код
 TwoClass &= NEW(MyClass)  ! здесь создается объект TwoClass - начинается его "время жизни"

 ! здесь выполняется какой то код

 DISPOSE(TwoClass)         ! уничтожается объект TwoClass - на этом его "время жизни" заканчивается
                           ! дальше невозможно использовать этот объект

 ! здесь выполняется какой то код

Одно из преимуществ описания класса, состоит в том, что вы можете использовать любые атрибуты,
которые доступны для слова CLASS (исключая атрибуты MODULE и TYPE). При описании (создании) объекта
вы можете указать атрибут THREAD, и не важно как описан родительский класс — с атрибутом THREAD или без него.
Конструкторы и деструкторы для классов с атрибутом THREAD вызываются для каждого
потока. Каждый новый поток получает новый экземпляр класса и переменных, описанных глобально с атрибутом THREAD.
Ядро клариона вызывает конструкторы для классов с атрибутом THREAD, когда поток запускается, и деструкторы,
когда поток закрывается. В предыдущих версиях Clarion конструкторы и деструкторы вызывались только когда
главный поток запускался и закрывался.

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

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

• Объект, определенный как ссылка на класс, инициализируется при использовании слова «NEW», а уничтожается
при вызове «DISPOSE».

• Объект, определенный в локальных данных, инициализируется после слова «CODE» (следующего за словом PROCEDURE)
и уничтожается при выполнении команды «RETURN» (явной или неявной), которая завершает работу процедуры.

Инициализация данных (свойств)
Для свойств, которые описаны как простые типы данных (например, LONG, STRING, DECIMAL и т.п.) память выделяется
автоматически и инициализируется нулями или пробелами (за исключением свойств с атрибутом AUTO) в момент появления
объекта в области видимости. Выделенная память возвращается операционной системе, когда объект
покидает область видимости.
Для свойств, определенных как ссылки (например, qCustomers &CustomersQueue, cRegistry &RegistryClass)
память не выделяется и свойства не инициализируются при появлении объекта в области видимости — для
инициализации вы должны использовать оператор «NEW». Также такие свойства не очищаются автоматически при
выходе объекта из области видимости, поэтому вы должны явно уничтожать объекты (созданные ранее по NEW) при помощи оператора
«DISPOSE».

Конструкторы и деструкторы
Метод класса, имеющий имя «Construct» — это метод конструктор, который вызывается автоматически,
когда объект появляется в области видимости, непосредственно после того как для всех свойств объекта
будет выделена память и они будут инициализированы. Конструктор не может принимать какие-либо параметры
и не может иметь параметр VIRTUAL. Вы можете явно вызвать метод-конструктор, помимо его автоматического вызова.

Если объект является экземпляром класса, который, в свою очередь, является дочерним классом, и оба класса —
и родительский и дочерний — имеют методы-конструкторы то:

• если дочерний метод-конструктор имеет атрибут REPLACE — тогда вызывается метод-конструктор родительского класса;

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

Метод класса, имеющий имя «Destruct» — это метод-деструктор, который вызывается автоматически когда объект
покидает область видимости, после того, как будет освобождена память для свойств объекта. Также как и конструктор, деструктор
не может принимать параметры. Вы можете вызвать метод деструктор явно, помимо его автоматического вызова.
(Какой метод-деструктор вызывать: дочерний или родительский определяется также как и для метода-конструктора)

PUBLIC, PRIVATE и PROTECTED (Инкапсуляция)
Public(общие, публичные)-свойства и методы класса — это все свойства и методы
описанные, без атрибутов PRIVATE и PROTECTED. Public-методы видны во всех других методах класса и во всех
методах дочерних классов и в любом коде, относящемся к области видимости класса.

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

Protected(защищенные)-свойства и методы определяются с атрибутом PROTECTED. Защищенные свойства и методы
видны в методах класса, в котором они определены и в дочерних методах класса.

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

MyClass CLASS
MyProc     PROCEDURE(LONG PassedVar) ! метод принимает один параметр
        END

Вы можете описать метод MyProc, например так:

MyClass.MyProc PROCEDURE(LONG PassedVar) ! описание метода начинается с имени класса
 CODE

или так:

MyProc PROCEDURE(MyClass SELF, LONG PassedVar) ! имя класса передается первым параметром
 CODE                                          ! используя слово SELF

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

Например, пусть есть следующее описание объекта:

MyClass  CLASS           ! без атрибута TYPE - описание класса - это и есть описание объекта
MyField     LONG
MyProc      PROCEDURE
         END

MyClass2 MyClass         ! еще одно описание объекта

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

MyClass.MyField = 10   ! использование свойства объекта MyClass
MyClass2.MyField = 10  ! использование свойства объекта MyClass2

Для вызова методов класса вы можете использовать правила синтаксиса языка или вызывать метод
класса как процедуру, передавая первым параметром сам класс (SELF). Например, для объекта MyClass, описанного
выше, вы можете вызвать метод как:

 CODE
 MyClass.MyProc

или так:

 CODE
 MyProc(MyClass)

SELF и PARENT
В методах класса, свойства и другие методы текущего экземпляра класса используются
с синтаксисом слова SELF в начале, вместо имени класса. Это позволяет обращаться к свойствам и методам текущего
экземпляра класса, в независимости от того какой это класс — родительский или дочерний. Это также служит
механизму, который позволяет родительскому классу вызывать виртуальные методы дочернего класса.

Например, для предыдущего примера, для того чтобы обратиться к свойству MyField в методе MyClass.MyProc вы должны использовать
следующий синтаксис:

MyClass.MyProc PROCEDURE
 CODE
 SELF.MyField = 10

Свойства и методы родительского класса могут быть явно вызваны из методов дочернего класса, используя синтаксис
слова PARENT вместо слова SELF, например:

MyDerivedClass.MyProc PROCEDURE

 CODE
 ! некоторой код можно написать здесь
 PARENT.MyProc       ! вызов родительского метода
 ! некоторой код можно написать здесь

Дополнительные примеры

! ниже представлено содержание файла ClassPrg.CLW
      PROGRAM
      MAP.                                       ! MAP обязательно для подключения BUILTINS.CLW

OneClass CLASS                                   ! базовый (родительский) класс
NameGroup GROUP                                  ! обращаться как OneClass.NameGroup
First      STRING(20)                            ! обращаться как OneClass.NameGroup.First
Last       STRING(20)                            ! обращаться как OneClass.NameGroup.Last
          END
BaseProc  PROCEDURE(REAL Parm)                   ! описание прототипа метода
Func      PROCEDURE(REAL Parm),STRING,VIRTUAL    ! описание прототипа метода с атрибутом VIRTUAL
Proc      PROCEDURE(REAL Parm),VIRTUAL           ! описание прототипа метода с атрибутом VIRTUAL
         END                                     ! окончание описания класса

TwoClass  CLASS(OneClass),MODULE('TwoClass.CLW') ! класс, наследуюемый от OneClass
Func       PROCEDURE(LONG Parm),STRING           ! переопределение OneClass.Func
Proc       PROCEDURE(STRING Msg,LONG Parm)       ! перегрузка родительской функции
          END

ClassThree CLASS(TwoClass),MODULE('Class3.CLW')  ! класс, наследуемый от TwoClass
Func        PROCEDURE(,LONG Parm),STRING,VIRTUAL
Proc        PROCEDURE(REAL Parm),VIRTUAL
           END

ClassFour  ClassThree                            ! экземпляр класса ClassThree
ClassFive  ClassThree                            ! экземпляр класса ClassThree

 CODE
 OneClass.NameGroup = '|OneClass Method'         ! присвоение свойств значениями
 TwoClass.NameGroup = '|TwoClass Method'
 ClassThree.NameGroup = '|ClassThree Method'
 ClassFour.NameGroup = '|ClassFour Method'

 MESSAGE(OneClass.NameGroup & OneClass.Func(1.0))! вызов метода OneClass.Func
 MESSAGE(TwoClass.NameGroup & TwoClass.Func(2))  ! вызов метода TwoClass.Func и т.п.
 MESSAGE(ClassThree.NameGroup & ClassThree.Func('|Call ClassThree.Func',3.0))
 MESSAGE(ClassFour.NameGroup & ClassFour.Func('|Call ClassFour.Func',4.0))

 OneClass.BaseProc(5)                            ! вызыв метода BaseProc из класса OneClass.Proc

 BaseProc(TwoClass,6)                            ! демонстрация вызовов разных методов
 TwoClass.Proc('Second Class',7)                 ! разными способами
 ClassThree.BaseProc(8)
 ClassFour.BaseProc(9)
 Proc(ClassFour,'Fourth Class',10)

OneClass.BaseProc   PROCEDURE(REAL Parm)         ! описание метода OneClass.BaseProc
 CODE
 MESSAGE(Parm & SELF.NameGroup &'|BaseProc executing|calling SELF.Proc Virtual method')
 SELF.Proc(Parm)                                 ! вызов виртуального метода
 MESSAGE(Parm & SELF.NameGroup&'|BaseProc executing|calling SELF.Func Virtual method')
 MESSAGE(SELF.NameGroup & SELF.Func(Parm))       ! вызов виртуального метода

OneClass.Func   PROCEDURE(REAL Parm)             ! описание метода OneClass.Func
 CODE
 RETURN('|Executing OneClass.Func - ' & Parm)

Proc       PROCEDURE(OneClass SELF,REAL Parm)    ! описание метода OneClass.Proc
 CODE
 MESSAGE(SELF.NameGroup & ' |Executing OneClass.Proc - ' & Parm)

! ниже представлено содержание файла TwoClass.CLW

  MEMBER('ClassPrg')

Func       PROCEDURE(TwoClass SELF,LONG Parm)      ! описание метода TwoClass.Func
 CODE
 RETURN('|Executing TwoClass.Func - ' & Parm)

TwoClass.Proc  PROCEDURE(STRING Msg,LONG Parm)     ! описание метода TwoClass.Proc
 CODE
 MESSAGE(Msg & '|Executing TwoClass.Proc - ' & Parm)

! ниже представлено содержание файла Class3.CLW

  MEMBER('ClassPrg')

ClassThree.Func  PROCEDURE(,LONG Parm) ! код метода ClassThree.Func
 CODE
 SELF.Proc(Msg,Parm)                               ! вызов TwoClass.Proc (перегрузка)
 RETURN(Msg & '|Executing ClassThree.Func - ' & Parm)

ClassThree.Proc  PROCEDURE(REAL Parm)              ! описание метода ClassThree.Proc
 CODE
 SELF.Proc('Called from ClassThree.Proc',Parm)     ! вызов метода TwoClass.Proc
 MESSAGE(SELF.NameGroup &' |Executing ClassThree.Proc - ' & Parm)