Классы в программных модулях
Классы
Понятие класса
Для поддержки ООП в язык Delphi введены объектные типы данных, с помощью которых одновременно описываются данные и операции над ними. Объектные типы данных называют классами, а их экземпляры — объектами.
Классы объектов определяются в секции type глобального блока.
Описание класса начинается с ключевого слова class и заканчивается ключевым словом end. По форме объявления классы похожи на обычные записи, но помимо полей данных могут содержать объявления пользовательских процедур и функций. Такие процедуры и функции обобщенно называют методами, они предназначены для выполнения над объектами различных операций.
Общий синтаксис объявления класса:
type className = class (ancestorClass) memberListend; |
MemberList служит для описания полей, методов и свойств.
Или:
Type TClassName = class(TParentClass) private ... { private declarations here} protected ... { protected declarations here } public ... { public declarations here } published ... { published declarations here } end; |
Замечания:
1. Если TparentClass не указан, то подразумевается Tobject, включая базовые constructor и destructor.
2. Методы описываются своим заголовком без тела подпрограммы.
3. При описании полей, свойств и методов можно разграничить доступ к этим атрибутам класса специальных ключевых слов: private, protected, public, published.
- Private. Все, что объявлено в секции private недоступно за пределами модуля. Секция private позволяет скрыть те поля и методы, которые относятся к так называемым особеностям реализации.
- Public. Поля, методы и свойства, объявленные в секцииpublic не имеют никаких ограничений на использование, т.е. всегда видны за пределами модуля. Все, что помещается в секцию public, служит для манипуляций с объектами и составляет программный интерфейс класса.
- Protected. Поля, методы и свойства, объявленные в секцииprotected, видны за пределами модуля только потомкам данного класса; остальным частям программы они не видны. Так же как и private, директива protected позволяет скрыть особенности реализации класса, но в отличие от нее разрешает другим программистам порождать новые классы и обращаться к полям, методам и свойствам, которые составляют так называемый интерфейс разработчика. В эту секцию обычно помещаются виртуальные методы.
- Published. Устанавливает правила видимости те же, что и директиваpublic. Особенность состоит в том, что для элементов, помещенных в секцию published, компилятор генерирует информацию о типах этих элементов. Эта информация доступна во время выполнения программы, что позволяет превращать объекты в компоненты визуальной среды разработки. Секцию published разрешено использовать только тогда, когда для самого класса или его предка включена директива компилятора $TYPEINFO.
4. Перечисленные секции могут чередоваться в объявлении класса в произвольном порядке, однако в пределах секции сначала следует описание полей, а потом методов и свойств.
5. Если в определении класса нет ключевых слов private, protected, public и published, то для обычных классов всем полям, методам и свойствам приписывается атрибут видимости public, а для тех классов, которые порождены от классов библиотеки VCL, — атрибут видимостиpublished.
6. Внутри модуля никакие ограничения на доступ к атрибутам классов, реализованных в этом же модуле, не действуют.
Приведем пример объявления класса:
TYPE TCoordinates=record X,Y:Integer; end; Tfigure = classprivateFcolor:TColor;Coords:TCoordinates; ProtectedProcedure setColor(c:TColor); virtual;Procedure Draw; virtual; abstract;Procedure Hide; virtual; abstract;Procedure Move(NewX,NewY:Integer);Property Color: TColor read Fcolor write setcolor;End; |
Класс содержит поля (Fcolor, Coords) и методы (Draw, Hide, Move). Заголовки методов, (всегда) следующие за списком полей, играют роль упреждающих (forward) описаний. Программный код методов пишется отдельно от определения класса.
В некоторых случаях требуется, чтобы объекты разных классов содержали ссылки друг на друга. Возникает проблема: объявление первого класса будет содержать ссылку на еще не определенный класс. Она решается с помощью упреждающего объявления:
type TReadersList = class; // упреждающее объявление класса TReadersList TDelimitedReader = class Owner: TReadersList; ... end; TReadersList = class Readers: array of TDelimitedReader; ... end; |
Первое объявление класса TDelimitedReader называется упреждающим (от англ. forward). Оно необходимо для того, чтобы компилятор нормально воспринял объявление поля Owner в классе TDelimitedReader.
Классы в программных модулях
Классы очень удобно собирать в модули. При этом их описание помещается в секцию interface, а код методов — в секцию implementation. Создавая модули классов, нужно придерживаться следующих правил:
- все классы, предназначенные для использования за пределами модуля, следует определять в секции interface;
- описание классов, предназначенных для употребления внутри модуля, следует располагать в секции implementation;
- если модуль B использует модуль A, то в модуле B можно определять классы, порожденные от классов модуля A.
Объекты
Чтобы от описания класса перейти к объекту, следует выполнить соответствующее объявление в секции var:
var Figure: TFigure; |
При работе с обычными типами данных этого объявления было бы достаточно для получения экземпляра типа. Однако объекты в среде Delphi являются динамическими данными, т.е. распределяются в динамической памяти. Поэтому переменная Figure — это просто ссылка на экземпляр (объект в памяти), которого физически еще не существует.
Чтобы сконструировать объект (выделить память для экземпляра) класса TFigure и связать с ним переменную Figure, нужно в тексте программы поместить следующий оператор:
Figure := TFigure.Create; |
Create — это так называемый конструктор объекта; он всегда присутствует в классе и служит для создания и инициализации экземпляров. При создании объекта в памяти выделяется место только для его полей. Методы, как и обычные процедуры и функции, помещаются в область кода программы; они умеют работать с любыми экземплярами своего класса и не дублируются в памяти.
После создания объект можно использовать в программе: получать и устанавливать значения его полей, вызывать его методы. Доступ к полям и методам объекта происходит с помощью уточненных имен, например:
Figure.Move; |
Кроме того, как и при работе с записями, допустимо использование оператора with, например:
with Figure do Move; |
Если объект становится ненужным, он должен быть удален вызовом специального метода Destroy, например:
Figure.Destroy; // Освобождение памяти, занимаемой объектом |
Destroy — это так называемый деструктор объекта; он присутствует в классе наряду с конструктором и служит для удаления объекта из динамической памяти. После вызова деструктора переменная Figure становится несвязанной и не должна использоваться для доступа к полям и методам уже несуществующего объекта. Чтобы отличать в программе связанные объектные переменные от несвязанных, последние следует инициализировать значением nil. Например, в следующем фрагменте обращение к деструктору Destroy выполняется только в том случае, если объект реально существует:
Figure := nil;...if Figure <> nil then Figure.Destroy; |
Вызов деструктора для несуществующих объектов недопустим и при выполнении программы приведет к ошибке.
Чтобы избавить программистов от лишних ошибок, в объекты ввели предопределенный метод Free, который следует вызывать вместо деструктора. Метод Free сам вызывает деструктор Destroy, но только в том случае, если значение объектной переменной не равно nil. Поэтому последнюю строчку в приведенном выше примере можно переписать следующим образом.
Figure.Free; |
После уничтожения объекта переменная Figure сохраняет свое значение, продолжая ссылаться на место в памяти, где объекта уже нет. Если эту переменную предполагается еще использовать, то желательно присвоить ей значение nil, чтобы программа могла проверить, существует объект или нет. Таким образом, наиболее правильная последовательность действий при уничтожении объекта должна быть следующая:
Figure.Free;Figure := nil; |
С помощью стандартной процедуры FreeAndNilэто можно сделать проще и элегантнее:
FreeAndNil(Figure); // Эквивалентно: Figure.Free; Figure := nil; |
Значение одной объектной переменной можно присвоить другой. При этом объект не копируется в памяти, а вторая переменная просто связывается с тем же объектом, что и первая:
var R1, R2: TFigure; // Переменные R1 и R2 не связаны с объектомbegin R1 := TFigure.Create; // Связывание переменной R1 с новым объектом // Переменная R2 пока еще не связана ни с каким объектом R2 := R1; // Связывание переменной R2 с тем же объектом, что и R1 // Теперь обе переменные связаны с одним объектом R2.Free; // Уничтожение объекта // Теперь R1 и R2 не связаны ни с каким объектом end; |
Объекты могут выступать в программе не только в качестве переменных, но также элементов массивов, полей записей, параметров процедур и функций. Кроме того, они могут служить полями других объектов. Во всех этих случаях программист фактически оперирует указателями на экземпляры объектов в динамической памяти. Следовательно, объекты изначально приспособлены для создания сложных динамических структур данных, таких как списки и деревья. Указатели на объекты для этого не нужны.
Итак, вы уже имеете некоторое представление об объектах, перейдем теперь к вопросу реализации их методов.