Private

End.

Repeat

Begin

Else

Begin

Begin

Begin

Begin

Begin

Begin

Begin

Begin

Begin

Var

End.

Repeat

Begin

Else

Begin

Begin

Begin

Begin

Begin

Begin

Begin

Type

Begin

Begin

Begin

Begin

a:=x; b:=y; Rad:=r;

end;

procedure Ring.Show;

SetColor(White);

Circle(a,b,Rad);

end;

procedure Ring.Hide;

SetColor(Black);

Circle(a,b,Rad);

end;

end;

Необхідно помітити, що методи об'єкта-нащадка заміняють собою (чи перевизначають) для нащадка однойменні методи батьківського об'єкта. Іншими словами, якщо має місце звертання до методу Rіng.Move, система дивиться, чи не заданий метод з таким ім'ям в описі об'єкта Rіng. Якщо так, використовується цей новий метод; якщо ні, використовується метод, успадкований від предка. (При цьому необхідно пам'ятати, що перевизначати можна тільки методи, імена полів в об'єкті-нащадку не повинні збігатися з іменами полів батьківського об'єкта.).

Після того як тип-нащадок оголошен, перш ніж приступити до маніпулювання його полями, необхідно створити екземпляр об'єкта:

var ring1:ring;

Віртуальні методи і поліморфізм

Давайте повернемося до методу Move з попередньої програми. От його опис:

procedure Dot.Move;

Hide;

a:=a+Da; b:=b+Db;

show

end;

Слід звернути увагу, на те, що з середини методу Move мають місце звертання до методів Hide і Show. Зміст цих звертань очевидний: спочатку об'єкт робиться невидимим на екрані (викликається метод Hide), потім переміщається (координати точки на екрані потрібним чином змінюються) і, нарешті, знову робиться видимим (викликається метод Show).

Але що відбудеться, якщо метод Move у даному виді використовувати в програмі з двома об'єктами: Dot і Ring? Згадаємо, що метод Move успадковується об'єктом Ring від об'єкта Dot, а методи Hide і Show, оскільки приховання і показ окружності на екрані здійснюється не так, як точки, в об'єкті Ring перевизначаються. Отож, для точки при цьому ніяких ускладнень не виникає. Точка без проблем "гасне", змінює координати і потім знову "запалюється", оскільки усі викликувані тут методи (спочатку Move, а з нього — Hide і Show) для об'єкта Dot є "рідними".

Що стосується кола, то при виклику методу Ring.Move система намагається знайти метод з таким ім'ям в описі об'єктного типу Ring і, не знайшовши його, продовжує пошук в об'єкті-предку. У результаті має місце звертання до методу Dot.Move. Після цього з методу Move повинні бути викликані (перевизначені) методи Ring.Hide і Ring.Show, однак цього не відбувається з успадкованого методу Dot.Move, екземпляр об'єкта Ringl, замість Ring.Hide і Ring.Show, викликає однойменні методи об'єкта Dot. Це пояснюється тим, що методи Dot.Move, Dot.Hide і Dot.Show жорстко зв'язані, оскільки вони були відкомпільовані в єдиному контексті — об'єктному типі Dot. Іншими словами, зв'язок між цими методами, що був встановлена при компіляції, має статичний характер. У цьому випадку виявиться переміщується також точка, а не коло. (Правда, текст відповідної програми тут не приводиться, однак читач може самостійно перевірити, що це саме так — для цього досить потрібним чином перетворити текст попередньої програми. При натисканні потрібних клавіш, що повинні б переміщати коло,, на екрані з'являється друга точка, що і переміщається замість кола.)

Як же зробити так, щоб методи Hide і Show викликалися в залежності від того, екземпляр якого об'єкта ініціював звертання до методу Move? Тут на допомогу приходить механізм, відомий як динамічне (чи пізнє) зв'язування— на відміну від статичного (чи раннього) зв'язування. Зазначений механізм реалізується за допомогою віртуальних методів.

Заголовок віртуального методу в описі об'єктного типу доповнюється зарезервованим словом VIRTUAL. Якщо в нащадках цього об'єктного типу маються перевизначальні методи (тобто методи з тим же ім'ям), вони також повинні бути оголошені як віртуальні і при цьому мати той же набір формальних параметрів, що і метод об'єкта-предка. От як виглядають опису об'єктних типів Dot і Rlng з віртуальними методами:

Dot= object

a, b: integer;

constructor Init (x,y: integer);

procedure Show; virtual;

procedure Hide; virtual;

procedure Move (Da, Db: integer);

end;

Ring = object (Dot)

Rad: integer;

constructor Init (x, y, r: integer);

procedure Show; virtual;

procedure Hide; virtual;

end;

(Тут CONSTRUCTOR— це зарезервоване слово, що позначає особливий вид процедури. Виклик віртуального методу повинний бути випереджений звертанням до такої процедури.)

Тепер повернемося до виклику екземпляром об'єкта Ring методу Move, a з нього — методів Hide і Show. Тут спочатку система звертається до методу Dot.Move (попередньо метод з таким ім'ям шукається в описі об'єкта Ring і, оскільки його тут ннем, має місце звертання до відповідного методу об'єкта-предка). Після цього для переміщення кола спочатку потрібно зробити його невидимим (викликати метод Ring.Hide), потім потрібним чином змінити координати центра і, нарешті, відобразити коло у новому місці на екрані (викликати метод Ring.Show). А оскільки при виконанні програми Turbo Pascal забезпечує звертання саме до того віртуального методу, що визначений для викликаючого об'єкта, з методу Dot.Move мають місце звертання до методів Ring.Hide і Ring.Show.

На закінчення приведемо повний текст програми, що відображає на екрані крапку і коло, які можна переміщати, натискаючи відповідні клавіші.

program ObjDotCirc;

uses crt, graph;

Dot= object

a, b: integer;

constructor Init (x,y: integer);

procedure Show; virtual;

procedure Hide; virtual;

procedure Move (Da, Db: integer);

end;

Ring = object (Dot)

Rad: integer;

constructor Init (x, y, r: integer);

procedure Show; virtual;

procedure Hide; virtual;

end;

constructor Dot.Init;

a:=x; b:=y;

end;

procedure Dot.Show;

PutPixel(a,b,White);

end;

procedure Dot.Hide;

PutPixel(a,b,Black);

end;

procedure Dot.Move;

Hide;

a:=a+Da; b:=b+Db;

Show

end;

constructor Ring.Init;

a:=x;

b:=y;

Rad:=r;

end;

procedure Ring.Show;

SetColor(White);

Circle(a,b,Rad);

end;

procedure Ring.Hide;

SetColor(Black);

Circle(a,b,Rad);

end;

var i,j,k,Err: integer;

a: char;

Dotl:Dot; Ringl:Ring;

begin {тіло програми}

i:=detect; initgraph(i,j,");

Err:=GraphResult;

If Err <> grOK then WriteLn (GraphErrorMsg (Err))

Dotl.Init(GetMaxX div 2, GetMaxY div 2);

Dotl.Show;

Ringl.Init(GetMaxX div 2, GetMaxY div 2, GetMaxY div 6);

Ringl.Show;

while KeyPressed do

a:=ReadKey;

a:=ReadKey;

case ord(a) of

72:Dotl.Move(0,-5);

80:Dotl.Move(0,5);

77:Dotl.Move(5,0);

75:Dotl.Move(-5,0);

73:Ringl.Move(0,-5);

81:Ringl.Move(0,5);

79:Ringl.Move(5,0); 71:Ringl.Move(-5,0);

end;

until a = chr(27);

end;

Дана програма відрізняється від попередньої програми, тільки тим, що тут додатково оголошено тип-нащадка Ring. Крім того, методи Show і Hide обох об'єктів оголошені як віртуальні, а методи Init перетворені в конструктори

Також оператор CASE у новій програмі доповнений наступними варіантами:

73:Ringl.Move(0,-5);

81:Ringl.Move(0,5);

79:Ringl.Move(5,0);

71:Ringl.Move(-5,0);

Представлені варіанти забезпечують переміщення кола по екрану в чотирьох напрямках. Переміщення має місце при натисканнях клавіш <PgUp> (нагору), <PgDn> (униз), <End> (вправо) і <Home> (уліво), що генерують скэн-коды відповідно 73, 81, 79і 71.

У цій програмі особливу увагу варто звернути на виклики методу Move у тілі оператора CASE. У перших чотирьох випадках має місце звертання до методу Dot.Move, а в інших випадках — до методу Ring.Move. Однак ми знаємо, що метод Move— єдиний. Він оголошений в об'єкті Dot і успадкований об'єктом Rіng. Іншими словами, той самий метод Move працює по-різному (переміщає точку чи коло), у залежності від того, який об'єкт його викликає. Така властивість об'єктів називається поліморфізмом.

Конструктори, динамічні об'єкти і деструктори

Якщо в об'єктному типі є хоча б один віртуальний метод, у ньому повинний бути і спеціальний метод, відомий як конструктор, який обов’язково повинний бути застосований до екземпляра об'єкта до першого звертання до віртуального методу. У силу цього конструктор звичайно являє собою метод, що задає для об'єкта деякі початкові значення (тобто виконуючий його ініціалізацію). Конструктор може бути визначений або в даному об'єкті, або успадкований від об'єкта-предка. При цьому сам конструктор віртуальним методом бути не може.

В описі об'єктного типу заголовок конструктора відрізняється від заголовка звичайного методу тільки тим, що в ньому зарезервоване слово PROCEDURE замінене на CONSTRUCTOR. Чим реально конструктор відрізняється від звичайного методу? Конструктор, крім описаних у ньому дій, установлює зв'язок між об'єктом і спеціальною таблицею віртуальних методів, що містить адреси кодів, які реалізують віртуальні методи.

Як уже відзначалося в цій главі вище, екземпляри об'єктних типів можуть визначатися як статичні (у розділі описів змінних) і як динамічні, причому останнє має місце найчастіше. От як можна створити динамічні екземпляри об'єктів Dotl і Rlngl:

Dotl: ^Dot

Ringl: ^Ring; begin

New (Dotl, lnit);

New (Ringl, Init);

Вище згадувалося, що якщо в об'єкті маються віртуальні методи, перед звертанням до них екземпляр даного об'єкта повинний викликати конструктор. Саме таким конструктором тут є Init, що викликається процедурою New (як свій другий параметр, використовуючи при цьому розширений синтаксис). (Приклад програми, у якій екземпляри об'єктів Dotl і Ringl оголошені в якості динамічних, мається наприкінці даного розділу.)

Після завершення роботи з динамічним об'єктом виділену для нього пам'ять варто звільнити. Це виконується за допомогою стандартної процедури DISPOSE:

Dispose (Dotl, Done);

Dispose (Ringl, Done);

Тут процедура DISPOSE звертається до деструктора DONE, що зазначений у якості її другого параметра. В описі об'єктного типу заголовок деструктора відрізняється від заголовка звичайного методу тільки тим, що в ньому зарезервоване слово PROCEDURE замінене на DESTRUCTOR. Однак, на відміну від конструктора, зарезервоване слово DESTRUCTOR є синонімом PROCEDURE. Іншими словами, метод, що завершує роботу з об'єктом, оформляється спеціальним зарезервованим словом тільки для того, щоб дотримати стилістичної симетрії (якщо є спеціальний починаючий метод, логіка диктує, що повинено бути і відповідний завершальний метод). Крім того, на відміну від конструкторів, деструктори можуть являти собою віртуальні методи. При цьому необхідно розуміти що виклик деструктора Done не як параметр процедури Dispose, а самого по собі, динамічну пам'ять не звільнить.На закінчення приведемо текст програми, аналогічної попередній, у якій об'єкти Dotl і Ringl оголошені в якості динамічних.

program ObjDotCircl;

uses crt, graph;

type Dot= object

a, b: integer;

constructor Init (x,y: integer);

procedure Show; virtual;

procedure Hide; virtual;

procedure Move (Da, Db: integer);

destructor Done;

end;

Ring = object (Dot);

Rad: integer;

constructor Init (x, y, z: integer);

procedure Show; virtual;

procedure Hide; virtual;

destructor Done;

end;

constructor Dot.Init;

a:=x;

b:=y;

end;

procedure Dot.Show;

PutPixel(a,b,White);

end;

procedure Dot.Hide;

PutPixel(a,b,0);

end;

procedure Dot.Move;

Hide;

a:=a+Da; b:=b+Db;

Show

end;

constructor Ring.Init;

a:=x;

b:=y;

Rad:=z;

end;

procedure Ring.Show;

SetColor(Black);

Circle(a,b,Rad);

end;

procedure Ring.Hide;

SetColor(0);

Circle{a,b,Rad);

end;

destructor Dot.Done;

Hide;

end;

destructor Ring.Done;

Hide;

end;

var i,j,k,Err: integer;

a:char;

Dotl:^Dot; Ringl:^Ring;

begin {тіло програми}

i:=detect;

initgraph(i,j,");

Err:=GraphResult;

If Err <> grOK then WriteLn (GraphErrorMsg(Err))

New(Dotl,Init(GetMaxX div 2, GetMaxY div 2));

Dotl^.Show;

New(Ringl,Init(GetMaxX div 2, GetMaxY div 2, GetMaxY div 6));

Ringl^.Show;

while KeyPressed do

a:=ReadKey;

a:=ReadKey;

case ord(a) of

72:Dotl^.Move(0,-5);

80:Dotl^.Move(0,5);

77:Dotl^.Move(5,0);

75:Dotl^.Move(-5,0);

73:Ringl^.Move(0,-5);

81:Ringl^.Move(0,5);

79:Ringr.Move(5,0);

71:Ringl^.Move(-5,0);

end;

until a = chr(27);

Dispose (Dotl, Done);

Dispose(Ringl,Done)

end;

У яких випадках має сенс використовувати динамічні змінні, ми з'ясували при вивченні покажчиків і використання динамічної пам'яті. Наприклад, якщо програма оперує множиною об'єктів (а не всього двома, як у нашому випадку) і звичайної (чи статичної) пам'яті для всіх цих об'єктів не вистачає, варто помістити їх у динамічну пам'ять. Крім того, якщо використання об'єкта починається в середині програми чи завершується задовго до її кінця, щоб не займати пам'ять увесь час, також доцільно оголосити такі об'єкти в якості динамічних. (Останнє для нашого приклада також не актуально, оскільки екземпляри об'єктів тут використовуються від початку і до кінця; тобто строго говорячи, пам'ять за допомогою деструкторів у нашій програмі можна було б і не звільняти.)

Поля і методи: сховані і загальнодоступні

Поля і методи в описі об'єктного типу можуть оголошуватися як сховані, або як загальнодоступні. Відповідні розділи в описі об'єкта відкриваються директивами PRTVATE і PUBLIC. От так можна використовувати директиви в описі типу Dot із програми, з яким ми мали справу в попередньому розділі:

Dot= object

a, b: integer;


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: