
- •Наследование
- •ПОНЯТИЕ НАСЛЕДОВАНИЯ
- •Пример с измерителями ресурсов
- •type
- •Code:
- •Повторение мелко
- •ПЕРЕКРЫТИЕ АТРИБУТОВ В НАСЛЕДНИКАХ
- •Как видно из примера,Повторениенаследнике можно вызвать п рекрытыймелкометод предка, указав перед именем метода
- •СОВМЕСТИМОСТЬ ОБЪЕКТОВ РАЗЛИЧНЫХ КЛАССОВ
- •КОНТРОЛЬ И
- •ВИРТУАЛЬНЫЕ МЕТОДЫ
- •Суть виртуальных методов в том, что они вызываются по фактическому типу экземпляра, а
- •АБСТРАКТНЫЕ ВИРТУАЛЬНЫЕ МЕТОДЫ
- •ДИНАМИЧЕСКИЕ МЕТОДЫ
Наследование
ПОНЯТИЕ НАСЛЕДОВАНИЯ
Классы инкапсулируют (т.е. включают в себя) поля, методы и свойства; это их первая черта. Следующая не менее
важная черта классов — способность наследовать поля, методы и свойства
других классов.

Пример с измерителями ресурсов
type |
|
TDiskGauge = class |
{ измеритель дискового пространства} |
DriveLetter: Char; |
{ буква дискового накопителя} |
PercentCritical: Integer; |
{ критический процент свободного пространства} |
function GetPercentFree: Integer; procedure CheckStatus;
end;
Класс TDiskGauge описывает измеритель дискового ресурса и непригоден для измерения ресурса другого типа, например оперативной памяти. С появлением измерителя оперативной памяти нужен новый класс объектов:
Code:
type
TMemoryGauge = class FPercentCritical: Integer; constructor Create;
function GetPercentFree: Integer;
procedure SetPercentCritical (Value: Integer) ; procedure CheckStatus;
property PercentFree: Integer read GetPercentFree; property PercentCritical: Integer
read FPercentCritical write SetPercentCritical; end;

type
TResourceGauge = class FPercentCritical: Integer; constructor Create;
function GetPercentFree: Integer;
procedure SetPercentCritical (Value: Integer) ; procedure CheckStatus;
property PercentFree : Integer read GetPercentFree; property PercentCritical: Integer
read FPercentCritical write SetPercentCritical; end;
constructor TResourceGauge.Create; begin
FPercentCritical := 10; end;
function TResourceGauge.GetPercentFree: Integer; begin
Result := 0; end;
procedure TResourceGauge.SetPercentCritical (Value: Integer); begin
if (Value >= 0) and (Value < 100) then FPercentCritical := Value; end;
procedure TResourceGauge.CheckStatus; begin
if GetPercentFree <= FPercentCritical then Beep; end;
Поля, методы и свойства класса TMemoryGauge
аналогичны тем, что определены в классе TDiskGauge. Отличие состоит в отсутствии поля DriveLetter и другой реализации конструктора
Create и метода GetPercentFree. Если в будущем появится класс, описывающий измеритель ресурса какого-то нового типа, то придется снова определять общие для всех классов поля, методы и свойства. Чтобы избавиться от дублирования атрибутов при определении новых классов, воспользуемся механизмом наследования. Прежде всего выделим
атрибуты, общие для всех измерителей ресурсов, в отдельный класс TResourceGauge:

Code:
type
TDiskGauge = class(TResourceGauge) DriveLetter: Char;
constructor Create (ADriveLetter: Char) ; function GetPercentFree: Integer;
end;
TMemoryGauge = class(TResourceGauge) function GetPercentFree: Integer;
end;
При реализации класса TResourceGauge ничего не
известно о том, что в действительности представляет собой ресурс, поэтому функция GetPercentFree возвращает нуль. Очевидно, что создавать объекты класса TResourceGauge
не имеет смысла. Для чего тогда нужен класс TResourceGauge? Ответ: чтобы на его основе породить
два других класса — TDiskGauge и TMemoryGauge,
описывающих конкретные виды измерителей ресурсов, — измеритель диска и измеритель памяти:
Классы TDiskGauge и TMemoryGauge определены как наследники
TResourceGauge (об этом говорит имя в скобках после слова
class). Они автоматически включают в себя все описания, сделанные в классе TResourceGauge и добавляют к ним некоторые новые. В результате формируется следующее дерево классов:
Класс, который наследует атрибуты другого класса, называется порожденным классом или потомком.
Естественно, что класс, от которого происходит наследование, выступает в роли базового, или предка. В примере класс TDiskGauge является непосредственным потомком класса TResourceGauge. Если от TDiskGauge породить новый класс, то он тоже будет потомком TResourceGauge, но уже не таким близким, как TDiskGauge.
Очень важно, что в отношениях наследования любой класс может иметь только одного
непосредственного предка и сколь угодно много потомков. Поэтому все связанные отношением наследования классы образуют иерархию. Примером иерархии классов является библиотека Visual
Component Library (VCL); с ее помощью в Delphi обеспечивается разработка Windows-приложений.

Повторение мелко
ПРЕДОК ПО УМОЛЧАНИЮ
В языке Object Pascal существует предопределенный класс TObject, который служит неявным предком тех классов, для которых предок не указан. Это означает, что объявление
Code:
type
TResourceGauge = class
...
end;
эквивалентно следующему:
Code:
type
TResourceGauge = class(TObject)
...
end;
Класс TObject выступает корнем любой иерархии классов. Он содержит ряд методов,
которые по наследству передаются всем остальным классам. Среди них конструктор Create, деструктор Destroy, процедура Free и некоторые другие методы.
Таким образом, полная иерархия классов для измерителей ресурсов выглядит так:

ПЕРЕКРЫТИЕ АТРИБУТОВ В НАСЛЕДНИКАХ
Вмеханизме наследования можно условно выделить три основных момента:
наследование полей;
наследование свойств; Наследование методов.
Любой порожденный класс наследует от родительского все |
|
||||
поля данных, поэтому классы TDiskGauge и TMemoryGauge |
Code: |
||||
автоматически содержат поле FPercentCritical, объявленное в |
|||||
классе TResourceGauge. Доступ к полям предка |
function TDiskGauge.GetPercentFree: Integer; |
||||
осуществляется по имени, как если бы они были определены в |
|||||
порожденном классе. В наследниках можно определять новые |
var |
||||
поля, но их имена должны отличаться от имен полей предка. |
Drive: Byte; |
||||
Наследование свойств и методов имеет свои особенности. |
begin |
||||
Drive := Ord(DriveLetter) - Ord('A') + 1; |
|||||
Свойство базового класса можно перекрыть (от англ. override) |
|||||
в производном классе, например чтобы добавить ему новый |
Result := DiskFree(Drive) * 100 div DiskSize(Drive) ; |
||||
end; |
|||||
атрибут доступа или связать с другим полем или методом. |
|
||||
Метод базового класса тоже можно перекрыть в производном |
function TMemoryGauge.GetPercentFree: Integer; { uses Windows; } |
||||
классе, например чтобы изменить логику его работы. |
var |
||||
Обратимся, |
например, |
к классам |
TDiskGauge и |
MemoryStatus: TMemoryStatus; |
|
TMemoryGauge. В них методы SetPercentCritical и |
begin |
||||
CheckStatus унаследованы от TResourceGauge, так как |
MemoryStatus.dwLength := SizeOf(MemoryStatus); |
||||
логика их работы не зависит от типа ресурса. А вот метод |
GlobalMemoryStatus(MemoryStatus); |
||||
GetPercentFree перекрыт, так как способ вычисления |
Result := 100 - MemoryStatus.dwMemoryLoad; |
||||
процента свободного пространства специфичен для диска и |
end; |
Code:
constructor TDiskGauge.Create (ADriveLetter: Char) ; begin
inherited Create; DriveLetter := ADriveLetter; end;
Как видно из примера,Повторениенаследнике можно вызвать п рекрытыймелкометод предка, указав перед именем метода зарезервированное слово inherited. Кстати, данный пример демонстрирует
важный принцип реализации конструкторов: сначала вызывается конструктор предка, а затем инициализируются дополнительные поля данных. В деструкторах применяется обратная последовательность действий: сначала разрушаются данные, недоступные предку, а затем вызывается унаследованный деструктор.

СОВМЕСТИМОСТЬ ОБЪЕКТОВ РАЗЛИЧНЫХ КЛАССОВ
Для классов, связанных отношением наследования, вводится новое правило
совместимости типов. Вместо объекта базового класса можно подставить объект любого производного класса. Обратное неверно. Например, переменной типа TResourceGauge можно присвоить значение переменной типа TDiskGauge:
Code:
var
R: TResourceGauge;
...
R := TDiskGauge.Create;
...
Объектная переменная R формально имеет тип TResourceGauge, а фактически связана с экземпляром класса TDiskGauge.
Правило совместимости классов чаще всего применяется при передаче объектов в
параметрах процедур и функций. Например, если процедура работает с объектом класса TResourceGauge, то вместо него можно передать объект класса TDiskGauge или
TMemoryGauge.

КОНТРОЛЬ И
ПРЕОБРАЗОВАНИЕ ТИПОВ
Поскольку реальный экземпляр объекта может оказаться наследником класса, указанного при описании объектной переменной или параметра, бывает необходимо проверить, к какому классу принадлежит объект на самом деле. Чтобы программист мог выполнять такого рода проверки, каждый объект хранит информацию о своем классе. В Object Pascal
существуют операторы is и as, с помощью которых выполняется соответственно проверка на тип (type checking) и преобразование к типу (type casting).
Например, чтобы выяснить, принадлежит ли некоторый объект Obj, объявленный в программе как
Code:
var
Obj: TObject;
к классу TResourceGauge или его наследнику, следует |
|
записать |
Code: |
|
if Obj is TResourceGauge then { да, принадлежит } ;
Для преобразования объекта к нужному типу используется оператор as, например:
Code:
with Obj as TResourceGauge do CheckStatus;
Стоит отметить, что для объектов применим и обычный способ приведения типа:
Code:
with TResourceGauge(Obj ) do CheckStatus;
Вариант с оператором as лучше, поскольку безопасен. Он генерирует ошибку (точнее, исключительную ситуацию) при
выполнении программы (run-time error), если реальный экземпляр объекта Obj несовместим с классом TResourceGauge. Забегая вперед, скажем, что ошибку приведения типа можно обработать и таким образом избежать
досрочного завершения приложения.