Информатика на пять О нас
 Добавить в избранное
5byte.ru
 Теория
 8 класс
 9 класс
 10 класс
 11 класс
Задания
 8 класс
 9 класс
 10 класс
 11 класс
Книги
Тесты
ЕГЭ
Turbo Pascal 7
 Описание
 Задачи
HTML
Рефераты

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

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

procedure tLine .NewEKPointPredEl, PointNextEl: Pointer) ;
        {PointPredEl - указатель на предыдущий элемент в структуре, PointNextEl - указатель на последующий элемент в структуре}
var NewPoint: Pointer;                 {указатель на новый элемент}
begin
   NewPoint :=,InitElem;
   PutConnecti'on( PointPredEl, NewPoint);
   PutConnec.tion(NewPoint, PointNextEl) ;
end;

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

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

Хотя написание двух методов NewEl для строки и всего текста эквивалентно, их нельзя объединить в один и поместить в объект-предок, т. к. обращение к InitElem у них представляет обращение к различным методам. Это неудобство можно обойти, объявив методы InitElem виртуальными. В этом случае, действительно, можно написать только один метод NewEl, а то, какой из методов InitElem будет выбираться в каждом конкретном случае, зависит от того, какой объект будет создаваться.

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

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

type
   tLine = object(tStructure)
       . . .
     function InitElem: Pointer; virtual;
       . . .
   end;
   tText = object(tStructure)
      . . .
     function InitElem: Pointer; virtual;
      . . .
   end;

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

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

function tStructure.InitElem : Pointer;
begin
end;

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

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

constructor tLine.Init;
begin
NewEl(nil, nil)
end;

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

New(PointText, Init);

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

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

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

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

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

uses Crt, Edit;  
{* * * Новая обработка клавиши Dn * * *}  
type  
  pInsertMyDn = ^tInsertMyDn; {указатель на новый тип}
  tInsertMyDn = object(tInsertDn) {новый тип}
    constructor Init;  
    procedure Ins; virtual  
  end;
var Dn1: pInsertMyDn; {новая переменная}
constructor tInsertMyDn.Init; {конструктор переменной нового типа}
begin  
end;  
procedure tInsertMyDn.Ins; {новый метод}
begin  
  with PointText^ do  
    if GetPointLine(GetY + 1) = nil then {если нет следующей строки...}
      NewEl (GetLastElem, nil); {- создать ее}
  tInsertDn.Ins {переместить курсор}
end;  
{* * * Основная программа * * *}  
begin  
  New(Dn1, Init); {создание нового объекта}
  TextBackGround(Blue);  
  TextColor(White);  
  ClrScr;  
  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: Dn1^.PutNewSymb; {обращение к новому методу}
         75: Left^.PutNewSymb;  
         77: Right^.PutNewSymb;  
        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.  

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

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

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

procedure tInsertMyDn.Ins; {новый метод}
begin
  with PointText^ do
    if GetPointLine(GetY + l)=nil then {если нет следующей, строки...}
      NewEl(GetLastElem, nil); {- создать ее}
  inherited Ins {переместить курсор}
end;  

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

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



uses Crt, Edit;  
{ * * * Обработка клавиш Home и End * * *}  
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(1)  
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;  
{* * * Основная программа * * *}  
begin  
  New(Home, Init); {создание нового объекта}
  New(Endd, Init); {создание нового объекта}
  TextBackGround(Blue);  
  TextColor(White);  
  GotoXY(l, 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; {обработка клавиши Home}
            79: Endd^. PutNewSymb; {обработка клавиши End}
          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.  




 У Вас есть материал пишите нам
 
    Copyright © 2008    
  Top.Mail.Ru