Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ООП / ООП_Лекции.doc
Скачиваний:
50
Добавлен:
08.06.2015
Размер:
1.03 Mб
Скачать

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

Любой класс может быть порожден от другого класса. Для этого при его объявлении указывается имя класса-родителя:

TChildClass = class(TParentClass)

Порожденный класс автоматически наследует поля, методы и свой­ства своего родителя и может добавлять их новыми. Таким образом, принцип наследования обеспечивает поэтапное создание сложных клас­сов и разработку собственных библиотек классов.

Все классы Object Pascal порождены от единственного родителя - класса TObject. Этот класс не имеет полей и свойств, но включает в себя методы самого общего назначения, обеспечивающие весь жизненный цикл любых объектов - от их создания до уничтожения. Программист не может создать класс, который не был бы дочерним классом TObject. Следующие два объявления идентичны:

TaClass = class (TObject)

TaClass = class

Принцип наследования приводит к созданию ветвящегося дерева классов, постепенно разрастающегося при перемещении от TObject к его потомкам. Каждый потомок дополняет возможности своего родите­ля новыми и передает их своим потомкам.

Для примера на рис. 13.1 показан небольшой фрагмент дерева клас­сов Delphi. Класс TPersistent обогащает возможности своего родителя TObject: он «умеет» сохранять данные в файле и получать их из него, в результате это умеют делать и все его потомки. Класс TComponent, в свою очередь, умеет взаимодействовать со средой разработчика и пере­дает это умение своим потомкам. TControl не только способен работать с файлами и средой разработчика, но он еще умеет создавать и обслу­живать видимые на экране изображения, а его потомок TWinControl может создавать Windows-окна и т.д.

Рис. 13.1. Фрагмент дерева классов Object Pascal

Полиморфизм

Полиморфизм - это свойство классов решать схожие по смыслу про­блемы разными способами. В рамках Object Pascal поведенческие свой­ства класса определяются набором входящих в него методов. Изменяя алгоритм того или иного метода в потомках класса, программист может придавать этим потомкам отсутствующие у родителя специфические свойства. Для изменения метода необходимоперекрыть его в потомке, т.е. объявить в потомке одноименный метод и реализовать в нем нуж­ные действия. В результате в объекте-родителе и объекте-потомке будут действовать дваодноименных метода, имеющих разную алгоритмическую основу и, следовательно, придающих объектам разные свойства. Это и называется полиморфизмом объектов.

В Object Pascal полиморфизм достигается не только описанным вы­ше механизмом наследования и перекрытия методов родителя, но и их виртуализацией (см. ниже), позволяющей родительским методам обра­щаться к методам своих потомков.

Составляющие класса Поля

Полями называются инкапсулированные в классе данные. Поля мо­гут быть любого типа, в том числе - классами, например:

type

TMyClass = class

alntField: Integer;

aStrField: String;

aObjField: TObject;

end;

Каждый объект получает уникальный набор полей, но общий для всех объектов данного класса набор методов и свойств. Фундаменталь­ный принцип инкапсуляции требует обращаться к полям только с по­мощью методов и свойств класса. Однако в Object Pascal разрешается обращаться к полям и напрямую:

var

aObject: TMyClass;

begin

aObject.alntField := 0;

aObject.aStrField := 'Строка символов';

end;

Класс-потомок получает все поля всех своих предков и может допол­нять их своими, но он не может переопределять их или удалять. Таким образом, чем ниже в дереве иерархии располагается класс, тем больше данных получают в свое распоряжение его объекты.

Методы

Инкапсулированные в классе процедуры и функции называются методами. Они объявляются так же, как и обычные подпрограммы:

type

TMyClass = class

Function MyFunc(aPar: Integer): Integer;

Procedure MyProc;

end;

Доступ к методам класса, как и к его полям, возможен с помощью составных имен:

var

aObject: TMyClass;

begin

aObject.MyProc;

end;

При описании нового класса можно добавлять новые методы и свойства, оставляя методы и свойства родителей, а можно роди­тельские методы и свойства переопределить или перегрузить.

Имеется четыре вида методов: статические, виртуальные, динамические и абстрактные.

По умолчанию все методы статические. Если в классе — на­следнике переопределить такой метод (ввести новый метод с тем же именем), то для объектов этого класса новый метод отменит родительский. Если обращаться к объекту этого класса, то вызы­ваться будет новый метод. Но если обратиться к объекту как к объекту родительского класса, то вызываться будет метод роди­теля. Замещение выполняется компилятором при трансляции программы.

В Object Pascal гораздо чаще используется динамическое замещение методов на этапе прогона программы.

Виртуальные и динамические методы не связаны с другими методами с тем же именем в классах — наследниках. Если в классах — наследниках эти методы перегружены, то при обра­щении к такому методу во время выполнения будет вызываться тот из методов с одинаковыми именами, который соответствует классу объекта, указанному при вызове. Например, если имеется базовый класс графических объектов TShape и ряд наследующих ему классов различных геометрических фигур, и если в каждомиз этих классов определен свой виртуальный метод Draw, рисую­щий эту фигуру, то можно написать в программе:

var ShapeArray: array[1..10] of TShape;

for i:=l to 10 do ShapeArray[i].Draw;

В этом коде в массив ShapeArray могут помещаться объекты разных классов, наследующих TShape. В цикле for обращение к объектам производится как к объектам базового для них типа TShape. В этом случае для каждого объекта будет вызываться виртуальный метод Draw именно этого объекта. Такой подход, облегчающий работу с множеством родственных объектов, назы­вается полиморфизмом.

При объявлении в классе виртуальных и динамических мето­дов после точки с запятой, завершающей объявление метода, до­бавляются ключевые слова virtual или dynamic. Например:

type

TShape = class

procedure Draw; virtual;

end;

Чтобы перегрузить в классе — наследнике виртуальный ме­тод, надо после его объявления поставит ключевое слово overri­de. Например:

type

TRectangle = class (TShape)

procedure Draw; override;

end;

TEllipse = class(TShape)

procedure Draw; override;

end;

Если в каком-то базовом классе метод был объявлен как виртуа­льный, то он остается виртуальным во всех классах — наследниках (в частности, и в наследниках классов наследников). Однако, обыч­но для облегчения понимания кодов перегруженные методы приня­то повторно объявлять виртуальными, чтобы была ясна их суть для тех, кто будет строить наследников данного класса. Например:

TEllipse = class(TShape)

procedure Draw; override; virtual;

end;

Описанным способом могут быть перегружены только виртуа­льные и динамические методы. Различие между динамическими и виртуальными методами невелико и относится к внутреннему механизму реализации их вызовов. Виртуальные методы эффек­тивнее с точки зрения временных затрат, а динамические — с точки зрения затрат памяти. В целом виртуальные методы обеспечивают более эффективный механизм полиморфизма, а дина­мические более выгодны, если в базовом классе определено много перегружаемых методов и они одновременно используются мно­гими объектами классов — наследников.

Для реализации этого метод, за­мещаемый в родительском классе, должен объявляться как динамический (с директивой dynamic) или виртуальный (virtual). Встретив такое объявление, компилятор создаст две таблицы - DMT (Dynamic Method Table) и VMT (Virtual Method Table) и поместит в них адреса точек входа соответственно динамических и виртуальных мето­дов. При каждом обращении к замещаемому методу компилятор встав­ляет код, позволяющий извлечь адрес точки входа в подпрограмму из той или иной таблицы. В классе-потомке замещающий метод объявля­ется с директивой override (перекрыть). Получив это указание, компи­лятор создаст код, который на этапе прогона программы поместит в ро­дительскую таблицу точку входа метода класса-потомка, что позволит родителю выполнить нужное действие с помощью нового метода.

Пусть, например, родительский класс с помощью методов Show и Hide соответственно показывает что-то на экране или прячет изображе­ние. Для создания изображения он использует метод Draw с логическим параметром:

type

TVisualObject = class(TWinControl)

Procedure Hide;

Procedure Show;

Procedure Draw(IsShow: Boolean); virtual;

end;

TVisualChildObject = class(TVisualObject)

Procedure Draw(IsShow: Boolean); override;

end;

Реализация методов Show и Hide очень проста:

Procedure TVisualObject.Show;

begin

Draw(True);

end;

Procedure TVisualObject.Hide;

begin

Draw(False);

end;

Методы Draw у родителя и потомка имеют разную реализацию и создают разные изображения. В результате родительские методы Show и Hide прятать или показывать те или иные изображения будут в зависи­мости от конкретной реализации метода Draw у любого из своих по­томков. Динамическое связывание в полной мере реализует полимор­физм классов.

Разница между динамическими и виртуальными методами состоит в том, что таблица динамических методов DMT содержит адреса только тех методов, которые объявлены как dynamic в данном классе, в то вре­мя как таблица VMT содержит адреса виртуальных методов не только данного класса, но и всех его родителей. Значительно большая по раз­меру таблица VMT обеспечивает более быстрый поиск, в то время как при обращении к динамическому методу программа сначала просмат­ривает таблицу DMT у объекта, затем - у его родительского класса и так далее, пока не будет найдена нужная точка входа.

Абстрактный метод — это виртуальный или динамический метод, реализация которого не определена в том классе, в кото­ром он объявлен. Предполагается, что этот метод будет перегру­жен в классах — наследниках. Только в тех классах, в которых он перегружен, его и можно вызывать.

Объявляется абстрактный метод с помощью ключевого слова abstract после слова virtual или dynamic. Например:

procedure DoSomething; virtual; abstract;

Динамически перекрываемые методы часто могут вообще ничего не делать. Такие методы называются абстрактными, они обязаны перекры­ваться в потомках. Программист может запретить вызов абстрактного метода, объявив его с директивой abstract. Например:

type

TVisualObject = class(TWinControl)

Procedure Draw(IsShow: Boolean); virtual; abstract;

end;

TVisualChildObject = class(TWinControl)

Procedure Draw(IsShow: Boolean); override;

end;

var

aVisualObject: TVisualObject; aVisualChild: TVisualChildObject;

begin

aVisualObject.Show;_{Ошибка! Обращение к абстрактному методу} aVisualChild.Show;{Нормальное обращение. Метод Draw у класса

TVisualChildObject перекрыт.}

end;

Обращение к неперекрытому абстрактному методу вызывает ошибку пе­риода исполнения. Разумеется, в грамотно составленной программе абстракт­ные методы никогда не вызываются. Классы, содержащие абстрактные мето­ды, называются абстрактными. Такие классы инкапсулируют общие свойства своих неабстрактных потомков, но объекты абстрактных классов никогда не создаются и не используются. Для эксплуатации абстрактных классов в биб­лиотеку классов Delphi включаются классы-потомки, в которых перекрывают­ся абстрактные методы родителя.

В состав любого класса входят два специальных метода - конструктор и деструктор. У класса TObject эти методы называются Create и Destroy, так же они называются в подавляющем большинстве его потомков. Конструктор распределяет объект в динамической памяти и помещает адрес этой памяти в переменную Self, которая автоматиче­ски объявляется в классе. Деструктор удаляет объект из кучи. Обраще­ние к конструктору должно предварять любое обращение к полям и не­которым методам объекта. По своей форме конструкторы и деструкторы являются процедурами, но объявляются с помощью зарезервирован­ных слов Constructor и Destructor:

type

TMyClass = class

IntField: Integer;

Constructor Create(Value: Integer);

Destructor Destroy;

end;

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

var

MyObject: TMyClass;

begin

MyObject.IntField := 0; // Ошибка! Объект не создан конструктором!

MyObject := TMyClass.Create; // Надо так: создаем объект

MyObject.IntField := 0; / /и обращаемся к его полю

MyObect.Free; // Уничтожаем ненужный объект

end;

В базовом классе TObject определен метод Free, который сначала проверяет действительность адреса объекта и лишь затем вызывает де­структор Destroy. Обращение к деструктору объекта будет ошибочным, если объект не создан конструктором, поэтому для уничтожения не­нужного объекта следует вызывать метод Free, как это сделано в пре­дыдущем примере.

Большинство конструкторов реализуют некоторые действия, необхо­димые для правильной работы объекта. Поэтому в конструкторе класса-потомка следует сначала вызвать конструктор своего родителя, а уже затем осуществлять дополнительные действия. Вызов любого метода родительского класса достигается с помощью зарезервированного слова Inherited (унаследованный):

Constructor TMyClass.Create(Value: Integer); // Возможная реализация конструктора

begin

Inherited Create; // Вызываем унаследованный кон­структор

IntField := Value; // Реализуем дополнительные действия

end;

Некоторые методы могут вызываться без создания и инициации объ­екта. Такие методы называются методами класса, они объявляются с помощью зарезервированного слова class:

type

TMyClass = class(TObject)

class Function GetClassName: String;

end;

var

S: String;

begin

S := TMyClass.GetClassName;

……..

end;

Методы класса не должны обращаться к полям, т.к. в общем случае вызываются без создания объекта, а следовательно, в момент вызова полей просто не существует. Обычно они возвращают служебную ин­формацию о классе - имя класса, имя его родительского класса, адрес метода и т.п.

Соседние файлы в папке ООП