Поля
Полями называются инкапсулированные в классе данные. Поля могут быть любого типа, в том числе - классами, например:
type TMyClass = class aIntField: Integer; aStrField: String; aObjField: TObject; end;
Каждый объект получает уникальный набор полей, но общий для всех объектов данного класса набор методов и свойств. Фундаментальный принцип инкапсуляции требует обращаться к полям только с помощью методов и свойств класса. Однако в Object Pascal разрешается обращаться к полям и напрямую:
type TMyClass = class FIntField: Integer; FStrField: String; end; var aObject: TMyClass; begin aObject.FIntField := 0; aObject.FStrField := 'Строка символов'; end;
Класс-потомок получает все поля всех своих предков и может дополнять их своими, но он не может переопределять их или удалять.
Таким образом, чем ниже в дереве иерархии располагается класс, тем больше данных получают в свое распоряжение его объекты.
Методы
Инкапсулированные в классе процедуры и функции называются методами. Они объявляются так же, как и обычные подпрограммы:
type TMyClass = class Function MyFunc(aPar: Integer): Integer; Procedure MyProc; end;
Доступ к методам класса, как и к его полям, возможен с помощью составных имен:
var aObject: TMyClass; begin aObject.MyProc; end;
Как уже говорилось, методы класса могут перекрываться в потомках. Например:
type TParentClass = class Procedure DoWork; end; TChildClass = class(TParentClass) Procedure DoWork; end;
Потомки обоих классов могут выполнять сходную по названию процедуру DoWork, но, в общем случае, будут это делать по-разному. Такое замещение методов называется статическим, т. к. реализуется компилятором.
В Object Pascal гораздо чаще используется динамическое замещение методов на этапе прогона программы. Для реализации этого метод, замещаемый в родительском классе, должен объявляться как динамический (с директивой dynamic) или виртуальный (virtual). Встретив такое объявление, компилятор создаст две таблицы -DMT (Dynamic Method Table) и VMT (Virtual Method Table) и поместит в них адреса точек входа соответственно динамических и виртуальных методов. При каждом обращении к замещаемому методу компилятор вставляет код, позволяющий извлечь адрес точки входа в подпрограмму из той или иной таблицы. В классе-потомке замещающий метод объявляется с директивой override (перекрыть). Получив это указание, компилятор создаст код, который на этапе прогона программы поместит в родительскую таблицу точку входа метода класса-потомка, что позволит родителю выполнить нужное действие с помощью нового метода.
Пусть, например, родительский класс с помощью методов show и Hide соответственно показывает что-то на экране или прячет изображение. Для создания изображения он использует метод Draw с логическим параметром:
type TVisualObject = class(TWinControl) Procedure Hide; Procedure Show; Procedure Draw(IsShow: Boolean); virtual; end;
TVisualChildObject = class(TVisualObject) Procedure Draw(IsShow: Boolean); override; end;
Реализация методов show и Hide очень проста:
Procedure TVisualObject.Show; begin Draw(True) ; end;
Procedure TVisualObject.Hide; begin Draw(False) ; end;
Методы Draw у родителя и потомка имеют разную реализацию и создают разные изображения. В результате родительские методы show и Hide - прятать или показывать те или иные изображения будут в зависимости от конкретной реализации метода Draw у-любого из своих потомков. Динамическое связывание в полной мере реализует полиморфизм классов.
Разница между динамическими и виртуальными методами состоит в том, что таблица динамических методов DMT содержит адреса только -тех методов, которые объявлены как dynamic в данном классе, в то время как таблица VMT содержит адреса виртуальных методов не только данного класса, но и всех его родителей. Значительно большая по размеру таблица VMT обеспечивает более быстрый поиск, в то время как при обращении к динамическому методу программа сначала просматривает таблицу DMT у объекта, затем -у его родительского класса и так далее, пока не будет найдена нужная точка входа.
Динамически перекрываемые методы часто могут вообще ничего не делать. Такие методы называются абстрактными, они обязаны перекрываться в потомках. Программист может запретить вызов абстрактного метода, объявив его с директивой abstract. Например:
type TVisualObject = class(TWinControl) Procedure Draw(IsShow: Boolean); virtual; abstract; end;
TVisualChildObject = class(TWinControl) Procedure Draw(IsShow: Boolean); override; end; var aVisualObject: TVisualObject; aVisualChild: TVisualChildObject ; begin aVisualObject.Show; {Ошибка/ Обращение к абстрактному методу} aVisualChild.Show; {Нормальное обращение. Метод Draw у класса TVisualChildObject перекрыт.) end;
Обращение к неперекрытому абстрактному методу вызывает ошибку периода исполнения. Разумеется, в грамотно составленной программе абстрактные методы никогда не вызываются. Классы, содержащие абстрактные методы, называются абстрактными. Такие классы инкапсулируют общие свойства своих неабстрактных потомков, но объекты абстрактных классов никогда не создаются и не используются. Для эксплуатации абстрактных классов в библиотеку классов Delphi включаются классы-потомки, в которых перекрываются абстрактные методы родителя.
В состав любого класса входят два специальных метода -конструктор и деструктор. У класса TObject эти методы называются create и Destroy, так же они называются в подавляющем большинстве его потомков. Конструктор распределяет объект в динамической памяти и помещает адрес этой памяти в переменную self, которая автоматически объявляется в классе. Деструктор удаляет объект из кучи. Обращение к конструктору должно предварять любое обращение к полям и некоторым методам объекта. По своей форме конструкторы и деструкторы являются процедурами, но объявляются с помощью зарезервированных слов constructor и Destructor:
type TMyClass = class IntField: Integer; Constructor Create(Value: Integer); Destructor Destroy; end;
Любые поля объекта, а также методы класса, оперирующие с его полями, могут вызываться только после создания объекта с помощью вызова конструктора, т. к. конструкторы распределяют объект в динамической памяти и делают действительным содержащийся в объекте указатель.
var MyObject: TMyClass;
begin MyObject.IntField := 0; { Ошибка! Объект не созданконструктором!} MyObject := TMyClass.Create; // Надо так: создаем объект MyObject.IntField := 0; // и обращаемся к его полю MyObect.Free; // Уничтожаем ненужный объект end;
В базовом классе TObject определен метод Free, который сначала проверяет действительность адреса объекта и лишь затем вызывает деструктор Destroy. Обращение к деструктору объекта будет ошибочным, если объект не создан конструктором, поэтому для уничтожения ненужного объекта следует вызывать метод Free, как это сделано в предыдущем примере.
Большинство конструкторов реализуют некоторые действия, необходимые для правильной работы объекта. Поэтому в конструкторе класса-потомка следует сначала вызвать конструктор своего родителя, а уже затем осуществлять дополнительные действия. Вызов любого метода родительского класса достигается с помощью зарезервированного слова inherited (унаследованный):
Constructor TMyClass.Create(Value: Integer); // Возможная реализация конструктора begin Inherited Create; // Вызываем унаследованный конструктор IntField := Value; // Реализуем дополнительные действия end;
Некоторые методы могут вызываться без создания и инициации объекта. Такие методы называются методами класса, они объявляются с помощью зарезервированного слова class:
type TMyClass = class(TObject) class Function GetClassName: String; end; var S: String;
begin S := TMyClass.GetClassName; end;
Методы класса не должны обращаться к полям, т. к. в общем случае вызываются без создания объекта, а следовательно, в момент вызова полей просто не существует. Обычно они возвращают служебную информацию о классе - имя класса, имя его родительского класса, адрес метода и т. п.
1 2 3
8 8 8
|