http://sulfurzona.com/
News
Service
Magazine
Software (Battle City Game, Wallpaper manager, Superpad, VG-NOW, Puzzle Game, Netler Internet Browser, ..)
Dune Game (Dune III, Dune IV, Cheats, Forum, ..)
Games free
Turbo Pascal (Assembler, Docs, Sources, Debbugers, ..)
Books (Docs for developers)
Forum
Guest book
Компьютерная диагностика двигателя автомобиля (адаптер К-линии)Компьютерная диагностика двигателя автомобиля (адаптер К-линии)
 
 
 
 

Паскаль для новичков (часть 35)

 

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

 
Паскаль для новичковИтак, в прошлый раз мы остановились на том, что для объекта TFrame переопределили унаследованные методы Create, Enable и Disable новыми одноимёнными методами, исходный код последних двух, как и обещал, привожу сейчас:
 
procedure TFrame.Enable;
begin
if FEnabled then exit;
FEnabled := true;
Graph.SetColor( FColor );
if FFilled then Bar(FLeft, FTop, FRight, FBottom)
    else Rectangle(FLeft, FTop, FRight, FBottom);
end;
 
procedure TFrame.Disable;
begin
if not FEnabled then exit;
FEnabled := false;
Graph.SetColor( GetBkColor );
if FFilled then Bar(FLeft, FTop, FRight, FBottom)
    else Rectangle(FLeft, FTop, FRight, FBottom);
end;
 
Код программы тестирования с незначительными изменениями:
 
var
    Frame: TFrame;
    j : integer;
 
Begin
InitGrp;
Frame.Create(100,100,200,200);
Frame.Enable;
readln;
for j := 1 to 100 do begin
      Frame.Move( 100 + j, 100 );
      {временная задержка 25 мс}
      NewDelay( 25 );
      end;
readln;
CloseGraph;
End.
 
При запуске тестовой программы будет видно, что после выполнения метода Enable на экране появилась рамка и, значит, метод составлен корректно. Но то, что мы увидим при выполнении оператора цикла - нас не устроит. А увидим мы плавное движение, но отнюдь не рамки, а всё той же линии.
  
Всё дело в унаследованном методе Move, который принадлежит типу-родителю TLine и связан с методами TLine.Enable и TLine.Disable статическим (ранним) связыванием, поэтому метод Move всегда будет перемещать линию. То есть выполнение данного метода будет проходить по такой схеме: Frame.Move > TLine.Move > TLine.Disable > … что идёт вразрез с нашими планами.
  
Вот незадача. Что же делать, ведь алгоритм метода Move на столько универсален, что переопределять его для объекта TFrame было бы напраслиной? Как раз именно для этого и существует возможность организовать динамическое (позднее) связывание методов при помощи объявления виртуальных методов.
  
Для объявления виртуального метода в описании объектного типа следует после заголовка метода указать служебное слово virtual. При чём одноимённые методы в родственных объектных типах должны быть виртуальными и, к тому же, иметь одинаковый интерфейс входных параметров. Стало быть, придётся немного модифицировать объявления наших объектных типов следующим образом:
 
type
       TLine = object
        FLeft, FTop, FRight, FBottom : integer;
        FColor : word;
        FEnabled : boolean;
        constructor Create( ALeft, ATop, ARight, ABottom : integer );
        procedure SetColor( AColor : word );
        procedure Enable; virtual;
        procedure Disable; virtual;
        procedure Move( ALeft, ATop : integer );
        function Left : integer;
        function Top : integer;
        function Right : integer;
        function Bottom : integer;
        function Enabled : boolean;
       end;
 
       TFrame = object (TLine)
         FFilled : boolean;
        constructor Create( ALeft, ATop, ARight, ABottom : integer );
        procedure SetFilled( AFilled : boolean );
        procedure Enable; virtual;
        procedure Disable; virtual;
        function Filled : boolean;
       end;
 
Вот теперь при выполнении цикла мы сможем с наслаждением созерцать плавное перемещение рамки. А весь фокус состоит в позднем связывании, когда унаследованный метод Move будет вызывать уже динамически связанные методы TFrame.Enable и TFrame.Disable. Это достигается тем, что методы Create были объявлены не при помощи служебного слова procedure, а при помощи служебного слова constructor. Объект, который содержит виртуальные методы, должен иметь хотя бы один особый метод, называемый конструктором. Метод-конструктор должен быть выполнен прежде других методов этого объекта. При его вызове выполняется специальный код (формируемый компилятором перед кодом тела конструктора), который обеспечивает динамическое связывание виртуальных методов. При чём метод-конструктор может быть унаследован, но не может быть виртуальным. Объект может иметь любое количество конструкторов.
  
Поэтому теперь метод Move будет перемещать то, что нужно объекту, который его вызывает. То есть выполнение данного метода будет проходить по такой схеме: Frame.Move > TLine.Move > TFrame.Disable > … что нам и требовалось.
  
Такое чудодейственное свойство называется полиморфизмом. Полиморфизм – это свойство, присущее объектам, имеющим одного общего родителя, позволяет решать подобные по смыслу задачи разными способами, за счёт перекрытия унаследованных методов и виртуализации. Таким образом, в объекте-предке и объекте-потомке будут работать два одноимённых метода, имеющие различную алгоритмическую основу, и наделяющие объекты разными свойствами. Вот что такое полиморфизм.
 

Динамические объекты

 
Ранее мы объявляли переменную объектного типа, как статическую. Помимо этого имеется возможность создать объект как динамическую переменную, что даёт некоторые преимущества перед статически образованными объектами. В частности станет возможным получить эффект от применения стандартной процедуры Fail, которая может вызываться только из конструктора объекта. Она освобождает уже выделенную конструктором память, завершает его работу и возвращает Nil в указатель на объект, тем самым, давая понять, что создание экземпляра объекта завершено аварийно. Конкретным примером может служить ситуация, когда конструктор должен выделить для нормального функционирования объекта буфер памяти. И вот при выделении памяти под буфер оказывается, что свободной памяти в куче (хипе) не хватает, тогда достаточно выполнить команду Fail, чтобы прервать инициализацию объекта и освободить память, выделенную под этот экземпляр.
  
Итак, для создания динамического экземпляра объекта следует описать переменную объекта, как указатель на объектный тип и затем использовать стандартную процедуру New, как показано в следующем примере:
 
type
       PFrame = ^TFrame;
var
      Frame : PFrame;
       j : integer;
begin
InitGrp;
If MemAvail < SizeOf( TFrame ) then begin
    CloseGraph;
    Writeln('Not enough memory');
    Halt;
    end;
New( Frame );
Frame^.Create(100,100,200,200);
Frame^.Enable;
readln;
for j := 1 to 100 do begin
     Frame^.Move( 100 + j, 100 );
     NewDelay( 25 );
     end;
readln;
CloseGraph;
end.
 
в котором принимаются меры по проверке свободной памяти перед выделением памяти для экземпляра объекта. Затем выделяется память и создаётся экземпляр командой New(Frame), затем проводится инициализация Frame^.Create(100,100,200,200), при чём Frame – это указатель, то, следовательно, для корректного обращения к свойствам экземпляра объекта, указатель следует разыменовать.
  
Данный пример работать будет, но если бы в теле конструктора Frame^.Create сработала команда Fail, то программа никак не смогла бы на это отреагировать, да и сам экземпляр был бы создан успешно, словно и не было никакой команды Fail. Поэтому существует иной способ создания экземпляра объекта при помощи расширенного интерфейса всё той же процедуры New, когда первым параметром идёт имя указателя на объект, а вторым имя конструктора:
 
New( Frame, Create(100,100,200,200) );
if Frame = nil then begin
   CloseGraph;
   Writeln('Object failed');
   Halt;
   end;
 
что позволяет совместить в одном операторе создание и инициализацию экземпляра объекта. При этом станет возможно проверить успешность данного предприятия.
  
Существует третий способ создания объекта посредством вызова команды New как функции, первым параметром которой должен быть идентификатор типа указателя на объект, вторым всё тот же вызов конструктора, а в качестве результата будет возвращён адрес созданного экземпляра объекта:
 
Frame := New( PFrame, Create(100,100,200,200) );
if Frame = nil then begin
 
 
 
 
   CloseGraph;
   Writeln('Object failed');
   Halt;
   end;
 
Для освобождения памяти, выделенной под экземпляр объекта, следует использовать стандартную процедуру Dispose, параметром которой может быть указатель на созданный экземпляр объекта, например Dispose(Frame). Такая операция освобождения памяти называется уничтожением экземпляра объекта.
  
Прежде, чем продолжить, рассмотрим такую ситуацию, когда необходимо передвигать рамку, но при этом не затирать изображение под ней. Совершенно очевидно, что объект TFrame не соответствует таким требованиям. Тогда создадим новый объект TWindow и, будем использовать такую замечательную наследственность:
 
type
       TWindow = object (TFrame)
        FImage : pointer;
        FImageSize : word;
        constructor Create( ALeft, ATop, ARight, ABottom : integer );
        procedure Enable; virtual;
        procedure Disable; virtual;
        destructor Free; virtual;
       end;
       PWindow = ^TWindow;
 
constructor TWindow.Create( ALeft, ATop, ARight, ABottom : integer );
begin
TFrame.Create( ALeft, ATop, ARight, ABottom );
FImageSize:=ImageSize(ALeft,ATop,ARight,ABottom);
{ прерваться при нехватке памяти }
if MemAvail < FImageSize then Fail;
GetMem( FImage, FImageSize );
end;
 
procedure TWindow.Enable;
begin
if FEnabled then exit;
FEnabled := true;
{ сохраняем затираемый фрагмент изображения }
GetImage( FLeft, FTop, FRight, FBottom, FImage^ );
{ рисуем рамку }
Graph.SetColor( FColor );
if FFilled then Bar(FLeft, FTop, FRight, FBottom)
    else Rectangle(FLeft, FTop, FRight, FBottom);
end;
 
procedure TWindow.Disable;
begin
if not FEnabled then exit;
FEnabled := false;
{ восстанавливаем затёртый фрагмент изображения }
PutImage( FLeft, FTop, FImage^, NormalPut );
end;
 
destructor TWindow.Free;
begin
{ освобождаем память перед уничтожением объекта }
FreeMem( FImage, FImageSize );
end;
 
Как вы могли заметить, в объекте TWindow появился новый метод, объявленный при помощи служебного слова destructor. Метод, объявленный таким способом, называется деструктором и служит для освобождения ресурсов, выделенных объектом для собственных нужд. Деструктор является, как бы, антиподом по отношению к конструктору, то есть завершает то, что конструктор начал, и делает это в обратном порядке. То есть, конструктор первым делом создаёт экземпляр объекта, выделяя память под него, затем выполняется динамическое связывание его методов, после чего выполняется тело конструктора, которое должно провести полную инициализацию полей объекта, и возможно получить память для полноценной работы. Деструктор делает прямо противоположные действия, с точностью до наоборот, соответственно, сперва выполняется тело деструктора, которое должно освободить всю полученную память кроме памяти самого экземпляра объекта, затем выполняется уничтожение экземпляра и освобождение памяти, выделенной под него.
  
Деструкторы могут быть виртуальными и могут наследоваться, кроме того, один объектный тип может иметь несколько деструкторов.
  
Так как объект TWindow при инициализации выделяет память под буфер FImage для хранения фрагмента изображения, то при уничтожении экземпляра объекта следует освободить эту память, иначе с многократным созданием и уничтожением таких объектов по ходу выполнения программы в один прекрасный момент хип может исчерпаться. Для этой цели служит метод-деструктор TWindow.Free, который следует вызывать перед уничтожением экземпляра объекта, например, так:
 
Window^.Free;
Dispose( Window );
 
хотя в процедуре Dispose допускается указывать в качестве второго параметра имя деструктора:
 
Dispose( Window, Free );
 
и это гораздо удобнее и нагляднее. А то, что метод TWindow.Free объявлен ещё и при помощи служебного слова virtual, добавит перспективу для потомков объектного типа TWindow.
 
Теперь наша тестовая программа, немного видоизменённая, продемонстрирует успешное выполнение поставленной задачи:
 
procedure Noise;
var x1, y1, x2, y2, C, j : integer;
begin
for j := 1 to 1000 do begin
      x1 := Random( GetMaxX + 1 );
      x2 := Random( GetMaxX + 1 );
      y1 := Random( GetMaxY + 1 );
      y2 := Random( GetMaxY + 1 );
      C := Random( White ) + 1;
      SetColor( C );
      Line( x1, y1, x2, y2 );
      end;
end;
 
var
    Window : PWindow;
    j : integer;
    mem1, mem2 : longint;
begin
InitGrp;
{ свободная память до создания объекта }
mem1 := MemAvail;
{ рисуем произвольное изображение на экране }
Noise;
If MemAvail < SizeOf( TWindow ) then begin
    CloseGraph;
    Writeln('Not enough memory');
    Halt;
    end;
Window := New( PWindow, Create(100,100,200,200) );
if Window = nil then begin
    CloseGraph;
    Writeln('Object failed');
    Halt;
    end;
Window^.Enable;
readln;
for j := 1 to 100 do begin
      Window^.Move( 100 + j, 100 );
      NewDelay( 25 );
      end;
readln;
Dispose( Window, Free );
{ свободная память после уничтожения объекта }
mem2 := MemAvail;
CloseGraph;
Writeln('Свободно памяти: было ', mem1,
              ' kb >> есть ', mem2, ' kb');
end.
 
Из показаний переменных mem1 и mem2 будет видно, что память освобождена объектом корректно.
 

Механизм работы объекта

 
Как это всё вертится?
  
По сути, объект представляет собой обыкновенную переменную похожую на запись. Код методов объекта хранится отдельно, как если бы это были простые процедуры и функции.
  
Всё дело в том, что в откомпилированном коде нет такого понятия как объекты - есть только исполняемый код и используемая память. Все методы объектов превращаются в обыкновенные подпрограммы. Такие подпрограммы помимо явных параметров получают в качестве первого параметра указатель на область памяти, где лежит созданный экземпляр объекта, поля которого они и используют. Этот параметр неявный и в теле метода может быть доступен через идентификатор Self – это ни что иное, как указатель на экземпляр объекта, метод которого выполняется в данный момент. После компиляции следующего метода:
 
procedure TWindow.Disable;
begin
if not FEnabled then exit;
FEnabled := false;
PutImage( FLeft, FTop, FImage^, NormalPut );
end;
 
получится приблизительно такая процедура:
 
procedure Disable( Self : TWindow );
begin
if not Self.FEnabled then exit;
Self.FEnabled := false;
PutImage( Self.FLeft, Self.FTop, Self.FImage^, NormalPut );
end;
 
а вызов метода
 
Window^.Disable;
 
в откомпилированной программе будет выглядеть так
 
Disable(Window^);
 
При необходимости программист может обращаться к элементам объекта так
 
procedure TWindow.Disable;
begin
if not Self.FEnabled then exit;
Self.FEnabled := false;
PutImage( Self.FLeft, Self.FTop, Self.FImage^, NormalPut );
end;
 
причём разыменование Self^ не нужно.
 

Скрытые поля и методы

 
При объявлении объектных типов имеется возможность объявить часть полей и методов как скрытые. Это позволяет ограничить видимость (доступность) скрытых полей и методов, если они используются вне модуля, в котором объявлены. Такие поля и методы считаются видимыми лишь в пределах программы или модуля, где они объявлены. Чтобы объявить поля и методы скрытыми достаточно их указать после служебного слова private:
 
type
       TObject = object
        { открытые поля и методы }
       private
        { скрытые поля и методы }
       end;
 
Если ненадолго вернуться к теме модулей, то следует сказать, что описание объектного типа может находиться как в интерфейсном блоке, так и в блоке реализации модуля, а методы этого объектного типа размещаются в блоке реализации.
 
Продолжение следует…
 
© Владислав Демьянишин
 
На нашем сайте можно не только бесплатно скачать игры, но и документацию и книги по программированию на MIDLetPascal, Turbo Pascal 6, Turbo Pascal 7, Borland Pascal, по программированию устройств Sound Blaster, Adlib, VESA BIOS, справочник Norton Guide и много другой полезной информации для программистов, включая примеры решения реальных задач по созданию резидентных программ. Предлагаю также посетить Марья искусница - сайт о рукоделии (http://mariya-iskusnica.ru).
 

Журнал > Программирование > Паскаль для новичков (Turbo Pascal, Assembler) > Паскаль для новичков (часть 35): Виртуальные методы
 
 
 
17
 
ВКонтакте
Facebook
 
 
 
На главную страницу На предыдущую страницу На начало страницы
 
 
Украинский портАл Украина онлайн Рейтинг@Mail.ru Рейтинг Сайтов YandeG Rambler's Top100