Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
выш.мат. методичка.DOC
Скачиваний:
32
Добавлен:
13.02.2015
Размер:
1.71 Mб
Скачать

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

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

рrосеdure tLine.NewЕl(РоintРrеdЕl, РоintNextЕ1: Роinter); {РоintРrеdЕl - указатель на предыдущий элемент в структуре, РоintNextEl - указатель на последующий элемент в структуре}

var NewPiont: Роinter {указатель, на новый элемент}

begin

NewPoint:=InitElem;

РutСоnnection(PointPredЕl, NewРоint);

РutСоnnection(NewPoint, PointNextEl);

еnd;

В этом методе первая строка (NеwРоint:= InitElem) - обращение к методу, выделяющему соответствующую область динамической памяти и размещающему там элемент строки, одновременно заполняя его строку информации Info пробелами. Две последующие сроки - обращение к методу РutСоnnection, устанавливающему связи между двумя элементами структуры (в данном случае - элементами строк). Эти два обращения устанавливают связи нового элемента строки с ее соседями: предыдущим и последующим элементами.

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

Хотя написание двух методов NewЕl для строки и всего текста эквивалентно их нельзя объединить в один и поместить в объект - предок, т. к. обращение InitElem у них представляет обращение к различным методам. Это неудобство можно обойти, объявив методы InitElem виртуальными. В этом случае, действи­тельно, можно написать только один метод NеwЕl, а то, какой из методов InitЕlеm будет выбираться в каждом конкретном случае, зависит от того, какой объект будет создаваться.

      1. Объявление виртуальных методов

Чтобы объявить метод виртуальным, при описании типа-объекта после указа­ния заголовка этого метода следует записать зарезервированное слово virtual:

tyре

tLinе = оbject(tStructure)

function InitЕlеm: Роinter; virtual;

еnd;

tТехt = оbject(tSructure)

function InitЕlеm: Роinter; virtual;

еnd;

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

Метод, использующий виртуальные методы, должен быть размещен в объекте, доступном всем объектам, где этот метод должен применяться. Так, в нашем примере метод NеwEl используется в объектах типов tLine и tТехt, которые яв­ляются непосредственными потомками абстрактного типа tStructure, поэтому ес­тественно NewEl описать в типе tStructure, что и сделано в примере. Однако в этом случае тип tStructure должен также содержать виртуальный метод InitElem. Но в связи с тем, что объект типа tStructure является абстрактным, этот метод не должен явно выполнять никаких действий (в каком-то смысле является фиктив­ным) и имеет вид:

function tStructure.InitЕlеm: Роintеr;

begin

еnd;

      1. Конструкторы и деструкторы.

Основное отличие виртуальных методов заключается в том. что необходимые связи с ними в программе устанавливаются не на этапе компиляции и компоновки, а на этапе выполнения программы. С этой целью у объекта создаются таблицы виртуальных методов, куда записываются адреса всех используемых в этом объекте виртуальных методов. При выполнении программы в случае необходимости из этой таблицы выбирается адрес соответствующего варианта виртуального метода с целью использования именно этого варианта. Однако для использования такой таблицы предварительно ее следует заполнить этими адресами. Для этой цели применяются специальные методы, которые называются конструкторами (соnstructor). Такой метод должен быть использован в программе до того, как будет обращение к виртуальному методу. Формальное отличие конструктора от обычного метода заключается в том, что вместо зарезервированного слова рrocedure используется зарезервированное слово соnstructor. Основное назначе­ние конструктора - записать адрес виртуального метода в таблицу виртуальных методов, однако он может выполнять и другие действия по инициализации созда­ваемого объекта: устанавливать необходимые связи, задавать начальные условия и: т. д. Так, в рассматриваемом примере конструктор тепа tLine (cтрока) форми­рует начальный элемент создаваемой строки и имеет вид:

Соnstructor tLine.Init;

begin

NewElem(nil, nil)

еnd;

Т. к. использовать конструктор следует в программе как можно раньше (во всяком случае до первого использования виртуального метода), то целесообраз­но его использование объединить непосредственно с созданием конкретного объекта. С этой целью в Тurbo Раscal стандартная процедура New дополнена вторым необязательным параметром - именем конструктора создаваемого объек­та. Так, в секции инициализации модуля, выполняемой при запуске программы, при создании объекта типа tТехt указатель на этот объект записывается в переменную РоintТехt одновременно происходит инициализация этого объекта конс­труктором Init:

New(PointТехt, Init);

Если при создании какого-либо объекта инициализировать его не нужно, а объект, тем не менее, содержит виртуальные методы, конструктор все-таки должен быть. Однако в этом случае он представляет собой пустую подпрограмму, не выполняющую явно никаких действий (см. соnstructor InsertUp.Init или подоб­ные ему), но заносящую информацию в таблицу виртуальных методов.

Аналогичным образом при удалении из динамической области памяти разме­щенного там объекта в процедуре Dispose можно использовать подпрограмму, называемую деструктором (destructor), предназначенную для выполнения разли­чных операций, связанных с ликвидацией объекта (исключение его из списка, задание параметров, очистка данных и т. д.). Деструктор, как правило, наследуе­тся потомками и обычно бывает виртуальным.

      1. Возможности модификации программы при иcпользовании виртуальных методов

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

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

usesСrt, Еdit; {*** Новая обработка клавишиDn***}

tyре

рInsertMyDn=^tInsertMyDn; {указатель на новый тип}

tInsertMyDn = object(tInsertDn) {новый тип}

соnstructor Init;

рrосеdure Ins; virtual;

еnd;

var Dn1: рInsertMyDn; {новая переменная}

соnstructortInsertМуDn.Init; {конструктор переменной нового типа}

begin

еnd;

рrосеduretInsertMyDn.Ins; {новый метод}

begin

with PointText^ do

if GetPointLine(GetY+1) = nil then {если нет следующей строки...}

NewEl(GetLastElem, nil); {- создать ее}

Insert.Ins{переместить курсор}

end;

{*** Основная программа ***}

begin

New(Dn1,Init); {создание нового объекта}

ТеxtBackGround(Blue);

ТeхtСоlоr(White);

СlrScr;

GotoXY(1, 1);

with PointText^ do

repeat

Сh := ReadКеу;

if Сh = Сhr(0) then

begin

Сh := ReadКеу;

case Ord(Ch) of

72: Up^.РutNewSymb;

80: Dn1^.РutNewSymb; (обращение к новому методу}

75: Left^.РutNewSymb;

77: Right^.РutNewSymb;

еnd

end

else if Сh >Сhr(31) then Symbol^.РutNewSymb

еlse if Ch=Сhr(Ent) then Еnter^.РutNewSymb

else if Сh=Сhr(ВасkS) then ВаскSpace^.РutNewSymb

until Сh = Сhr(Esc);

Dispose(РоintТехt, Done);

ТехtВасkground(Вlack);

СlrScr

еnd.

Здесь введен новый тип tInsertMyDn, являющийся наследником типа tInsertDn. Его виртуальный модуль Ins сначала создает новую строку, если ее нет, а затем обращается к уже существующему методу tInsertDn.Ins для перемещения курсо­ра в новое положение. Изменена в тексте и строка, где происходит обращение к методу обработки клавиши Dn.

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

Следует обратить внимание еще на. один момент, связанный .с методом tInsertMyDn.Ins. Этот метод сначала выполняет какие-то свои операции, а затем вызывает аналогичный метод своего предка. Такая ситуация допустима и встре­чается довольно часто. Это также позволяет несколько сократить программу, не программируя повторно операции метода предка. Т.к. эта ситуация встречается довольно часто, в версии 7.0 допустимо при обращении к методу непосредствен­ного предка не указывать его тип, а заменить его зарезервированным словом inherited. Так, в предыдущем примере рассматриваемый метод можно также за­писать следующим образом:

рrосеdure tInsertMyDn.Ins; {новый метод}

begin

with РоintТехt^do

if GetPointLine(GetY + 1)= nil then {если нет следующей строки…}

NewEl(GetLastElem, nil); {- создать ее}

InheritedIns{переместить курсор}

еnd;

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

Виртуальные методы следует использовать и в том случае, когда возможно ра­сширение функций программы. Так в рассматриваемом примере, конечно, мож­но ожидать расширения количества клавиш, обрабатываемых редактором. В связи с этим все методы обработки тех или иных клавиш объявлены виртуальными. Это позволяет довольно просто добавлять объекты обработки новых клавиш опять - таки без перекомпиляции уже созданного модуля. Так, в случае, когда необходимо дополнительно обрабатывать клавиши Ноme и Еnd, основная прог­рамма может выглядеть, например, следующим образом:

uses Crt, Edit;

(* * * Обработка клавиш Ноmе и Еnd * * *}

type

pInsertHome = ^tInsertHome; {указатель на новый тип}

tInsertHome = object(tText) {новый тип}

constructor Init;

procedure Ins; virtual

end;

pInsertEnd = ^tInsertEnd; {указатель на новый тип}

tInsertEnd = object(tText) {новый тип}

constructor Init;

procedure Ins; virtual

end;

var Home: pInsertHome; {новая переменная}

Endd: pInsertEnd; {новая переменная}

constructor tInsertHome.Init; {конструктор переменной нового типа}

begin

end;

procedure tInsertHome.Ins; {новый метод}

begin

with PointText^ do

PutX(l)

end;

constructor tInsertEnd.Init; {конструктор переменной нового типа}

begin

end;

procedure tInsertEnd.Ins; {новый метод}

var PointElLine: pElLine;

NomAbs: Word;

NomInEl: Byte;

begin

with PointText^ do

begin

CurrentPointLine^.LastNotBlank(PointElLine, NomAbs.NomInEl);

PutX (NomAbs + 1)

end

end;

( * * Ocновная пpoгрaмма * * *}

begin

New(Home, Init); {создание нового объекта}

New(Endd, Init); {создание нового объекта}

TextBackGround(Blue);

TextColor(White);

GotoXY(1, 1);

with PointText^ do

repeat

Ch = ReadKey;

if Ch = Chr(0) then

begin

Ch := ReadKey;

case Ord(Ch) of

72 Up^.PutNewSymb;

80 Dn^.PutNewSymb;

75 Left^.PutNewSymb;

77 Right^.PutNewSymb;

71 Home^.PutNewSymb; {Обработка клавиши Ноmе}

79 Endd^.PutNewSymb {Обработка клавиши Еnd}

end

end

else if Ch > Chr(31) then Symbol^.PutNewSymb

else if Ch = Chr(Ent) then Enter^.PutNewSymb

else if Ch = Chr(BackS) then Backspace^.PutNewSymb

until Ch = Chr(Esc);

Dispose(PointText, Done);

TextBackGround(Black);

ClrScr

end.