- •Объектно-ориентированное программирование
- •Часть 1 классы и объекты
- •Введение
- •1. Классы и объекты
- •Var aLine: tLine;
- •Var aColorLine: tColorLine;
- •2. Методы
- •2.1. Методы-функции и методы-процедуры
- •2.2. Конструкторы и деструкторы
- •Inherited Create;
- •Var TmpFrm: tForm;
- •Var Mem: tMemo;
- •2.3. Классовые процедуры и функции
- •Var s: String;
- •2.4. Скрытый Self
- •3. Видимость компонентов класса
- •4. Наследование
- •4.1. Основные понятия
- •4.2. Наследование полей
- •4.3. Поведение методов при наследовании
- •Var SomeObject: t1;
- •Virtual;
- •Virtual; Abstract;
- •4.4. Иерархия классов
- •4.5. Rtti
- •4.6. Проверка типа
- •4.7. Приведение типа
- •4.8. Указатели на класс
- •Var ObjRef: tObjRef;
- •Implementation
- •X, y, w, h: Integer): tControl;
- •5. Полиморфизм
- •6. Свойства (properties)
- •6.1. Объявление свойств
- •6.2. Объявления свойств-массивов
- •Var I: Byte;
- •6.3. Раздел Read
- •6.4. Раздел Write
- •Inherited Create;
- •Inherited Destroy;
- •6.5. Команды Default, NoDefault и Stored
- •6.6. Команда Index
- •Var aYear, aMonth, aDay: Word;
- •Var aYear, aMonth, aDay: Word;
- •6.7. Команды DispId и Implements
- •6.8. Переопределение свойств при наследовании
- •7. События (events)
- •7.1. Объявление событий
- •IfAssigned(fOnMouseMove) Then fOnMouseMove(Self, Shift, X, y);
- •Vk_return: NumMemo.DoNumStr(l);
- •Vk_return: PostMessage(NumMemo.Handle, wm_user1,1, 0);
- •7.2. Обработчики событий
- •7.3. Делегирование событий
- •Var Objl: tIstClass;
- •7.4. Переопределение стандартных событий
- •Var NewBtn: tNewButton;
4. Наследование
4.1. Основные понятия
Классы могут быть связаны между собой различными способами. Одна из фундаментальных концепций ООП - это понятие наследования классов, устанавливающее между двумя классами отношения "родитель-потомок".
Наследование (субклассирование) - это способность одного класса использовать характеристики другого. Оно использует отношение "вид чего-либо" между двумя классами. Этот простой принцип означает, что если вы хотите создать новый класс, лишь немного отличающийся от известного, то нет необходимости в переписывании заново полей и методов уже существующих у класса-предка.
Предок (ancestor) - это класс, представляющий свои возможности и характеристики другим классам через механизм наследования.
Класс, который использует характеристики другого класса посредством наследования, называется его потомком (descendant).
Непосредственный предок, от которого данный класс прямо происходит, называется родителем (parent).
Примечания:
• ОР поддерживает модель наследования, известную как простое наследование, которое ограничивает число родителей конкретного класса одним. Другими словами, пользовательский класс может иметь только одного родителя.
• Особенностью наследования является то, что:
• оно расширяет возможности в том смысле, что любой класс-потомок имеет доступ или наследует практически все ресурсы (методы, поля и свойства) родительского класса и всех его предков до самого верхнего уровня иерархии, т.е. до класса TObject. Наследование поддерживает повторное использование кода у потомков;
• наследование позволяет потомку усовершенствовать родительский класс, сделав его более специализированным. В определении класса-потомка указываются лишь новые методы, замещающие методы и новые поля.
• В классах-потомках можно переносить объявления из одного раздела в другой, с большей видимостью, кроме раздела Private, из которого в разделы с большей видимостью не переносят.
• Следует выбирать родительский класс таким образом, чтобы основное соотношение "вид чего-либо" соблюдалось. Например, нельзя сказать "Принтер - это вид дисплея", но можно сказать "Принтер - это вид выходного устройства ".
4.2. Наследование полей
Примечания:
• Поля, унаследованные от класса-родителя, располагаются перед новыми.
• В классе-потомке можно заместить наследуемый метод, но нельзя отказаться от наследования какого-либо поля.
• В классе-потомке можно объявить одноименное поле другого типа и этим скрыть прямой доступ к переопределенному полю. Тем не менее доступ будет возможен к обоим полям:
• к новому - напрямую:
ОЬ/.<поле>;
• к переопределенному - используя приведение типов (typecast):
TParentClass(Obj). <поле>;
• При переопределении поля можно перенести его объявление в раздел класса с другой видимостью, однако нельзя перенести объявления полей из раздела Private.
4.3. Поведение методов при наследовании
По тому, какие действия происходят при вызове, методы делятся на четыре группы: Static (статические). Virtual (виртуальные). Dynamic (динамические), Abstract (абстрактные). Одной из проблем наследования является диспетчеризация вызовов методов объектов, под которой понимается то, каким образом приложение будет определять какой код требуется выполнить при вызове того или иного метода. Во многом это определяется видом метода.
а) Статические методы.
По умолчанию все методы класса являются статическими. Транслятор разрешает вызовы статических методов на фазе трансляции. К моменту запуска программы адреса вызова статических методов уже известны и, поэтому они вызываются быстрее других. Этот механизм называют еще ранним связыванием (early binding).
Примечания:
• Статический метод можно скрыть, если определить новый виртуальный или динамический метод с тем же именем.
• Статический метод можно переопределить, если в наследуемом классе объявить новый статический метод с тем же именем. Можно изменять при переопределении и список параметров.
• Доступ к скрытому или переопределенному статическому методу возможен с помощью механизма приведения типов:
TParentClass(Obj). <метод>;
• Вызов статического метода определяется исключительно типом объектной переменной. Тип самого объекта, на который эта переменная в данный момент ссылается, не имеет значения, т.е. при вызове статического метода фактический класс объекта игнорируется1'.
б) Виртуальные методы.
Принципиально от статических отличаются вызовы виртуальных и динамических методов. Адреса этих методов определяются лишь во время выполнения программы из специальной таблицы. Такой поиск называют еще поздним связыванием (late binding). Решение о вызове конкретного метода решается в процессе выполнения программы, и решение основывается на данных, хранящихся в объекте, вызывающем метод. Таким образом, появляется возможность реализовать различные варианты поведения объектов разных классов при вызове методов с одним именем. Это, по сути, определение полиморфизма.
Когда компилятор встречает обращение к виртуальному методу, он подставляет вместо обращения к конкретному адресу код-смещение относительно начала специальной таблицы, из который и извлекается нужный адрес. Эта таблица называется таблицей виртуальных методов (Virtual Method Table, VMT). Такая таблица есть у каждого класса. В ней хранятся указатели (адреса) всех виртуальных методов класса: вновь объявленных и унаследованных. Отсюда достоинства и недостатки: они вызываются сравнительно быстро, хотя и медленнее статических, однако для хранения указателей требуется большее количество памяти. Так, если в базовом классе 5 виртуальных методов и у этого класса 5 производных классов, то фактически будет 30 указателей, содержащих почти идентичную информацию, которые займут 120 байт памяти в куче (Heap).
Доступ к виртуальному методу через VMT - это извлечение адреса по коду (смещению) из таблицы, состоящее из одной машинной инструкции.
Примечания:
• Для объявления метода виртуальным служит ключевое слово-директива Virtual, которое указывается в конце объявления заголовка метода после точки с запятой.
• Директива Virtual всегда вводит новый виртуальный метод, никак не
' Если в программе встречается обращение Obj.StaticMethod, где Obj - переменная TClassType, то генерируется вызов метода TCIassType.StaticMethod или ближайший StaticMethod, наследуемый классом TCIauType. Переменная Obj может ссылаться на один из потомков класса, производного от TClassType, в котором определен свой StaticMethod, т.е. фактически будет использоваться экземпляр дочернего класса. Тем не менее будет вызван именно TClassType.StaticMethod, т.е. для дочернего класса будет вызываться метод родителя связанный с любым другим одноименным методом, наследуемым классом. Переопределенный виртуальный метод ведет себя аналогично переопределенному статическому методу, т.е. полиморфизм не проявляется. Переопределить виртуальный метод в потомке можно и статическим методом.
Type T1=Class
Procedure Test; Virtual;
End;
T2=Class(T1)
Procedure Test; // Test скрывается, но не замещается
End;