Одноименные методы
Begin
Var
Type
Begin
Begin
Var
Type
Begin
Var
Type
Begin
Begin
Type
Type
Begin
Var
Type
Методы
Begin
Var
Type
Tуре
Поля
Полиморфизм
Полиморфизмом называется свойство классов решать схожие по смыслу задачи разными способами.
Поведенческие свойства класса (его функциональность) определяются набором входящих в него методов. Изменяя алгоритм того или иного метода в потомках класса, можно придавать этим потомкам отсутствующие у родителя специфические свойства.
Для изменения метода необходимо перекрыть его в потомке, то есть объявить одноименный метод и реализовать в нем нужные действия. В результате в объекте-родителе и объекте-потомке будут действовать два одноименных метода, имеющие разную алгоритмическую основу и придающие объектам разные свойства. Это и есть полиморфизм объектов.
В Delphi полиморфизм достигается не только описанным выше механизмом наследования и перекрытия методов родителя, но и их виртуализацией, позволяющей родительским методам обращаться к методам своих потомков.
Составляющие класса
Полями называются инкапсулированные в классе данные. Поля могут быть любого типа, в том числе — классами, например:
ТМуСIаss = сlass
...
aIntField: Integer;
aStrField: String;
aObjField: TObject;
...
end;
Каждый объект получает уникальный набор полей, но общий для всех объектов данного класса набор методов и свойств.
Фундаментальный принцип инкапсуляции требует обращаться к полям только с помощью методов и свойств класса. Однако в Delphi разрешается обращаться к полям и напрямую:
TMyClass = class
FlntField: Integer;
FStrField: String;
...
end;
aObject: TMyClass;
...
aObject.FIntField := 0;
aObject.FStrField := 'Строка символов';
...
end;
Класс-потомок получает все поля всех своих предков и может дополнять их своими полями, но он не может переопределять их или удалять.
Инкапсулированные в классе процедуры и функции называются методами. Они объявляются так же, как и обычные подпрограммы:
TMyClass = class
FunctionMyFunc(aPar: Integer): Integer;
ProcedureMyProc;
end;
Доступ к методам класса, как и к его полям, возможен с помощью составных имен:
aObject: TMyClass;
...
aObject.MyProc;
...
end;
Методы класса могут перекрываться в потомках. Например:
TParentClass = class
Procedure DoWork;
end;
TChildClass = class(TParentClass)
Procedure DoWork;
end;
Потомки обоих классов смогут выполнять сходную по названию процедуру DoWork, но, будут это делать по-разному. Такое перекрытие методов реализуется компилятором и называется статическим.
В Delphi чаще используется динамическое перекрытие методов на этапе прогона программы.
Для этого метод, перекрываемый в родительском классе, должен объявляться как динамический (с директивой dynamic) или виртуальный (virtual). Встретив такое объявление, компилятор создает две таблицы - DМТ (Dynamic Method Таblе) и VМТ (Virtual Method Таblе) и помещает в них адреса точек входа соответственно динамических и виртуальных методов. При каждом обращении к перекрываемому методу компилятор вставляет код, позволяющий извлечь из той или иной таблицы адрес точки входа в подпрограмму.
В классе-потомке перекрывающий метод объявляется с директивой override (перекрыть). Получив это указание, компилятор создает код, который на этапе прогона программы помещает в родительскую таблицу точку входа метода класса-потомка, что позволят родителю выполнить нужное действие с помощью нового метода.
//
Например, пусть родительский класс с помощью методов Show и Hide соответственно показывает что-то на экране или прячет изображение. Для создания изображения он использует метод Draw с логическим параметром:
TVisualObject = class(TWinControl)
Procedure Hide;
Procedure Show;
ProcedureDraw(IsShow: Boolean); virtual;
end;
TVisualChildObject = class(TVisualObject)
Procedure Draw(IsShow: Boolean); override;
end;
Реализация методов Show и Hide:
Procedure TVisualObject.Show;
Draw(True);
end;
Procedure TVisualObject.Hide;
Draw(False);
end;
Методы Draw у родителя и потомка имеют разную реализацию и создают разные изображения. В результате родительские методы Show и Hide будут прятать или показывать те или иные изображения в зависимости от конкретной реализации метода Draw у любого из своих потомков. Динамическое связывание в полной мере реализует полиморфизм классов.
Разница между динамическими и виртуальными методами заключается в том, что таблица динамических методов (DМТ) содержит адреса только тех методов, которые объявлены с директивой dynamic в данном классе, в то время как таблица VМТ содержит адреса виртуальных методов не только данного класса, но и его родителей. Значительно большая по размеру таблица VМТ обеспечивает быстрый поиск, в то время как при обращении к динамическому методу программа сначала просматривает таблицу DМТ у объекта, затем - у его родительского класса и так далее, пока не будет найдена нужная точка входа.
Динамически перекрываемые методы часто могут вообще ничего не делать. Такие методы называются абстрактными, они обязаны перекрываться в потомках. Можно запретить вызов абстрактного метода, объявив его с директивой abstract. Например:
TVisualObject = class(TWinControl)
...
Procedure Draw(IsShow: Boolean); virtual; abstract;
end;
TVisualChildObject = class(TWinControl)
...
Procedure Draw(IsShow: Boolean); override;
end;
aVisualObject: TVisualObject;
aVisualChild: TVisualChildObject;
...
aVisualObject.Show; {Ошибочное обращение к абстрактному
методу}
aVisualChild.Show; {Верное обращение. Метод Draw
у класса TVisualChildObject перекрыт.}
...
end;
Обращение к неперекрытому абстрактному методу вызывает ошибку периода исполнения. Классы, содержащие абстрактные методы, называются абстрактными. Такие классы инкапсулируют общие свойства своих неабстрактных потомков, но объекты абстрактных классов никогда не создаются и не используются. Для эксплуатации абстрактных классов в библиотеку классов Delphi включаются классы-потомки, в которых перекрываются абстрактные методы родителя.//
В состав любого класса входят два специальных метода - конструктор и деструктор. У класса TObject эти методы называются Create и Destroy, также они называются в подавляющем большинстве его потомков.
Конструктор распределяет объект в динамической памяти (размещаются лишь поля объекта, его методы являются общими для всех объектов данного класса и в кучу не переносятся) и помещает адрес этой памяти в переменную Sеlf, которая автоматически объявляется в классе. Обращение к конструктору должно предварять любое обращение к полям и некоторым методам объекта.
Деструктор удаляет объект из кучи. Конструкторы и деструкторы являются процедурами, но объявляются с помощью зарезервированных слов Constructor и Destructor:
TMyClass = class
IntField: Integer;
Constructor Create(Value: Integer);
Destructor Destroy;
end;
Любые поля объекта, а также методы класса, оперирующие с его полями, могут вызываться только после создания объекта путем вызова конструктора, так как конструкторы распределяют объект в динамической памяти и делают действительным содержащийся в объекте указатель и автоматически объявляемую переменную Self.
MyObject: TMyClass;
MyObject.IntField := 0; {Ошибка! Объект не создан
конструктором! }
MyObject := TMyClass.Create; { Правильное обращение:
создается объект}
MyObject.IntField := 0; {Затем можно обращаемся к его
полю}
...
MyObect.Free; // Уничтожаем ненужный объект
end;
Обращение к деструктору объекта будет ошибочным, если объект не создан конструктором, поэтому для уничтожения объекта следует вызывать метод Free, который определен в базовом классе TObject.Он сначала проверяет действительность адреса объекта и лишь, затем вызывает деструктор Destroy.
Специально для уничтожения объектов в модуле System определена процедура FreeAndNil, которая не только уничтожает объект, но и помещает в его указатель (им является идентификатор объекта) значение NIL:
FreeAndNil(MyObject)
Большинство конструкторов классов реализуют некоторые действия, необходимые для правильной работы его объекта. Поэтому в конструкторе класса-потомка сначала вызывается конструктор своего родителя, а уже затем осуществляются дополнительные действия.
Вызов любого метода родительского класса достигается с помощью зарезервированного слова - Inherited (унаследованный):
Constructor TMyClass.Create(Value: Integer);
// Возможная реализация конструктора
Inherited Create; // Вызываем унаследованный конструктор
IntField :=Value; // Реализуем дополнительные действия
end;
Некоторые методы могут вызываться без создания и инициализации объекта. Такие методы называются методами класса, они объявляются с помощью зарезервированного слова class:
TMyClass = class(TObject)
class Function GetClassName: String;
end;
S: String;
S := TMyClass.GetClassName;
...
end;
Методы класса не должны обращаться к полям, так как в общем случае вызываются без создания объекта, а, следовательно, в момент вызова полей просто не существуют. Обычно они возвращают служебную информацию о классе – имя класса, имя его родительского класса, адрес метода и т. п.
В Delphi можно в рамках одного класса объявлять несколько одноименных методов. Механизм перекрытия родительского метода одноименным методом потомка приводит к тому, что потомок «не видит» перекрытый родительский метод и может обращаться к нему лишь с помощью зарезервированного слова Inherited.
В Delphi введено зарезервированное слово overload (перезагрузить), с помощью которого становятся видны одноименные методы, как родителя, так и потомка. Чтобы одноименные методы можно было отличить друг от друга, каждый из них должен иметь уникальный набор параметров.
В ходе выполнения программы при обращении к одному из одноименных методов программа проверяет тип и количество фактических параметров обращения и выбирает нужный метод.
При обнаружении одноименного метода компилятор Delphi предупреждает о том, что у класса уже есть аналогичный метод с другими параметрами. Для подавления сообщений объявление одноименного метода можно сопровождать зарезервированным словом reintroduce(вновь ввести).
Например, поместим на пустую форму четыре кнопки TButton и напишем для них такие обработчики их событий OnClick:
procedureTForml.Button1Click(Sender: TObject);