Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Ответы к ПиОА (теория).doc
Скачиваний:
100
Добавлен:
28.10.2018
Размер:
9.91 Mб
Скачать

28) Наследование

Классы инкапсулируют (т.е. включают в себя) поля, методы и свойства; это их первая черта. Следующая не менее важная черта классов — способность наследовать поля, методы и свойства других классов. Чтобы пояснить сущность наследования обратимся к примеру с читателем текстовых файлов в формате "delimited text".

Класс TDelimitedReader описывает объекты для чтения из текстового файла элементов, разделенных некоторым символом. Он не пригоден для чтения элементов, хранящихся в другом формате, например в формате с фиксированным количеством символов для каждого элемента. Для этого необходим другой класс:

Поля, свойства и методы класса TFixedReader практически полностью аналогичны тем, что определены в классе TDelimitedReader. Отличие состоит в отсутствии свойства Delimiter, наличии поля FItemWidths (для хранения размеров элементов), другой реализации метода ParseLine и немного отличающемся конструкторе. Если в будущем появится класс для чтения элементов из файла еще одного формата (например, зашифрованного текста), то придется снова определять общие для всех классов поля, методы и свойства. Чтобы избавиться от дублирования общих атрибутов (полей, свойств и методов) при определении новых классов, воспользуемся механизмом наследования. Прежде всего, выделим в отдельный класс TTextReader общие атрибуты всех классов, предназначенных для чтения элементов из текстовых файлов. Реализация методов TTextReader, кроме метода ParseLine, полностью идентична реализации TDelimitedReader, приведенной в предыдущем разделе.

Классы TDelimitedReader и TFixedReader определены как наследники TTextReader (об этом говорит имя в скобках после слова class). Они автоматически включают в себя все описания, сделанные в классе TTextReader и добавляют к ним некоторые новые. В результате формируется дерево классов, показанное на рисунке 3.1 (оно всегда рисуется перевернутым).

Рисунок 3.1. Дерево классов

Класс, который наследует атрибуты другого класса, называется порожденным классом или потомком. Соответственно класс, от которого происходит наследование, выступает в роли базового, или предка. В нашем примере класс TDelimitedReader является прямым потомком класса TTextReader. Если от TDelimitedReader породить новый класс, то он тоже будет потомком класса TTextReader, но уже не прямым.

Очень важно, что в отношениях наследования любой класс может иметь только одного непосредственного предка и сколь угодно много потомков. Поэтому все связанные отношением наследования классы образуют иерархию. Примером иерархии классов является библиотека VCL; с ее помощью в среде Delphi обеспечивается разработка GUI-приложений.

//Пример написан в Вольвачёве глава 3,7 но как сказал Грошев он сложный в понимании и написан криво

29) Перекрытие атрибутов в наследниках

В механизме наследования можно условно выделить три основных момента:

  • наследование полей;

  • наследование свойств;

  • наследование методов.

Любой порожденный класс наследует от родительского все поля данных, поэтому классы TDelimitedReader и TFixedReader автоматически содержат поля FFile, FActive и FItems, объявленные в классе TTextReader. Доступ к полям предка осуществляется по имени, как если бы они были определены в потомке. В потомках можно определять новые поля, но их имена должны отличаться от имен полей предка.

Наследование свойств и методов имеет свои особенности.

Свойство базового класса можно перекрыть (от англ. override) в производном классе, например чтобы добавить ему новый атрибут доступа или связать с другим полем или методом.

Метод базового класса тоже можно перекрыть в производном классе, например чтобы изменить логику его работы. Обратимся к классам TDelimitedReader и TFixedReader. В них методы PutItem, GetItem, SetActive и GetEndOfFile унаследованы от TTextReader, поскольку логика их работы не зависит от того, в каком формате хранятся данные в файле. А вот метод ParseLine перекрыт, так как способ разбора строк зависит от формата данных:

function TDelimitedReader.ParseLine(const Line: string): Integer;

var

S: string;

P: Integer;

begin

S := Line;

Result := 0;

repeat

P := Pos(Delimiter, S); // Поиск разделителя

if P = 0 then // Если разделитель не найден, то считается, что

P := Length(S) + 1; // разделитель находится за последним символом

PutItem(Result, Copy(S, 1, P - 1)); // Установка элемента

Delete(S, 1, P); // Удаление элемента из строки

Result := Result + 1; // Переход к следующему элементу

until S = ''; // Пока в строке есть символы

end;

function TFixedReader.ParseLine(const Line: string): Integer;

var

I, P: Integer;

begin

P := 1;

for I := 0 to High(FItemWidths) do

begin

PutItem(I, Copy(Line, P, FItemWidths[I])); // Установка элемента

P := P + FItemWidths[I]; // Переход к следующему элементу

end;

Result := Length(FItemWidths); // Количество элементов постоянно

end;

В классах TDelimitedReader и TFixedReader перекрыт еще и конструктор Create. Это необходимо для инициализации специфических полей этих классов (поля FDelimiter в классе TDelimitedReader и поля FItemWidths в классе TFixedReader):

constructor TDelimitedReader.Create(const FileName: string;

const ADelimiter: Char = ';');

begin

inherited Create(FileName);

FDelimiter := ADelimiter;

end;

constructor TFixedReader.Create(const FileName: string;

const AItemWidths: array of Integer);

var

I: Integer;

begin

inherited Create(FileName);

// Копирование AItemWidths в FItemWidths

SetLength(FItemWidths, Length(AItemWidths));

for I := 0 to High(AItemWidths) do

FItemWidths[I] := AItemWidths[I];

end;

Как видно из примера, в наследнике можно вызвать перекрытый метод предка, указав перед именем метода зарезервированное слово inherited. Когда метод предка полностью совпадает с методом потомка по формату заголовка, то можно использовать более короткую запись. Воспользуемся ей и перепишем деструктор в классе TTextReader правильно:

destructor TTextReader.Destroy;

begin

Active := False;

inherited; // Эквивалентно: inherited Destroy;

end;

Два последних примера демонстрируют важный принцип реализации конструкторов и деструкторов. В конструкторах сначала вызывается конструктор предка, а затем инициализируются дополнительные поля данных. В деструкторах применяется обратная последовательность действий: сначала разрушаются данные, недоступные предку, а затем вызывается унаследованный деструктор. Всегда пользуйтесь этими правилами в своих программах, чтобы избежать ошибок.