Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология
Образование Политология Производство Психология Стандартизация Технологии


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



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

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

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

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

Любой порожденный класс наследует от родительского все поля данных, поэтому классы 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;

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

Совместимость объектов различных классов

Для классов, связанных отношением наследования, вводится новое правило совместимости типов. Вместо объекта базового класса можно подставить объект любого производного класса. Обратное неверно. Например, переменной типа TTextReader можно присвоить значение переменной типа TDelimitedReader:

var Reader: TTextReader; ... Reader: = TDelimitedReader.Create('MyData.del', '; ');

Объектная переменная Reader формально имеет тип TTextReader, а фактически связана с экземпляром класса TDelimitedReader.

Правило совместимости классов чаще всего применяется при передаче объектов в параметрах процедур и функций. Например, если процедура работает с объектом класса TTextReader, то вместо него можно передать объект класса TDelimitedReader или TFixedReader.

Заметим, что все объекты являются представителями известного вам класса TObject. Поэтому любой объект любого класса можно использовать как объект класса TObject.

3.7.5. Контроль и преобразование типов

Поскольку реальный экземпляр объекта может оказаться наследником класса, указанного при описании объектной переменной или параметра, бывает необходимо проверить, к какому классу принадлежит объект на самом деле. Чтобы программист мог выполнять такого рода проверки, каждый объект хранит информацию о своем классе. В языке Delphi существуют операторы is и as, с помощью которых выполняется соответственно проверка на тип (type checking) и преобразование к типу (type casting).

Например, чтобы выяснить, принадлежит ли некоторый объект Obj к классу TTextReader или его наследнику, следует использовать оператор is:

var Obj: TObject; ... if Obj is TTextReader then...

Для преобразования объекта к нужному типу используется оператор as, например

with Obj as TTextReader do Active: = False;

Стоит отметить, что для объектов применим и обычный способ приведения типа:

with TTextReader(Obj) do Active: = False;

Вариант с оператором as лучше, поскольку безопасен. Он генерирует ошибку (точнее исключительную ситуацию; об исключительных ситуациях мы расскажем в главе 4) при выполнении программы (run-time error), если реальный экземпляр объекта Obj не совместим с классом TTextReader. Забегая вперед, скажем, что ошибку приведения типа можно обработать и таким образом избежать досрочного завершения программы.

Виртуальные методы

Понятие виртуального метода

Все методы, которые до сих пор рассматривались, имеют одну общую черту — все они статические. При обращении к статическому методу компилятор точно знает класс, которому данный метод принадлежит. Поэтому, например, обращение к статическому методу ParseLine в методе NextLine (принадлежащем классу TTextReader) компилируется в вызов TTextReader.ParseLine:

function TTextReader.NextLine: Boolean; var S: string; N: Integer; begin Result: = not EndOfFile; if Result then begin Readln(FFile, S); N: = ParseLine(S); // Компилируется в вызов TTextReader.ParseLine(S); if N < > ItemCount then SetLength(FItems, N); end; end;

В результате метод NextLine работает неправильно в наследниках класса TTextReader, так как внутри него вызов перекрытого метода ParseLine не происходит. Конечно, в классах TDelimitedReader и TFixedReader можно продублировать все методы и свойства, которые прямо или косвенно вызывают ParseLine, но при этом теряются преимущества наследования, и мы возвращаемся к тому, что необходимо описать два класса, в которых большая часть кода идентична. ООП предлагает изящное решение этой проблемы — метод ParseLine всего-навсего объявляется виртуальным:

type TTextReader = class ... function ParseLine(const Line: string): Integer; virtual; //Виртуальный метод ... end;

Объявление виртуального метода в базовом классе выполняется с помощью ключевого слова virtual, а его перекрытие в производных классах — с помощью ключевого слова override. Перекрытый метод должен иметь точно такой же формат (список параметров, а для функций еще и тип возвращаемого значения), что и перекрываемый:

type TDelimitedReader = class(TTextReader) ... function ParseLine(const Line: string): Integer; override; ... end;   TFixedReader = class(TTextReader) ... function ParseLine(const Line: string): Integer; override; ... end;

Суть виртуальных методов в том, что они вызываются по фактическому типу экземпляра, а не по формальному типу, записанному в программе. Поэтому после сделанных изменений метод NextLine будет работать так, как ожидает программист:

function TTextReader.NextLine: Boolean; var S: string; N: Integer; begin Result: = not EndOfFile; if Result then begin Readln(FFile, S); N: = ParseLine(S); // Работает как < фактический класс>.ParseLine(S) if N < > ItemCount then SetLength(FItems, N); end; end;

Работа виртуальных методов основана на механизме позднего связывания (late binding). В отличие от раннего связывания (early binding), характерного для статических методов, позднее связывание основано на вычислении адреса вызываемого метода при выполнении программы. Адрес метода вычисляется по хранящемуся в каждом объекте описателю класса.

Благодаря механизму наследования и виртуальных методов в среде Delphi реализуется такая концепция ООП как полиморфизм. Полиморфизм существенно облегчает труд программиста, поскольку обеспечивает повторное использование кода уже написанных и отлаженных методов.


Поделиться:



Популярное:

Последнее изменение этой страницы: 2016-07-14; Просмотров: 559; Нарушение авторского права страницы


lektsia.com 2007 - 2024 год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! (0.018 с.)
Главная | Случайная страница | Обратная связь