Појам виртуелних метода¶
У примеру са правоугаоницима смо видели да без детаљног разумевања начина на који функционише модификација базне класе лако може да дође до погрешне употребе изведене класе и, последично, до погрешних резултата. Да би описани проблеми били превазиђени, потребан нам је нови механизам, који ће да узме у обзир динамичку природу објеката, односно њихов стварни тип у тренутку извршавања дате наредбе, у којој се позива спорни метод. Ту могућност нам доносе виртуелни методи.
Виртуелни методи су начин да компајлеру кажемо да приликом приступања члановима класе који у суштини представљају функције (методи, својства, индексери) не проналази одговарајући члан на основу декларације (тј. типа референце), већ на основу стварног типа актуелног објекта.
Активирањем механизма виртуелних метода не само да се отклањају претходно описани проблеми, него
се отварају и нове могућности заједничке употребе објеката базне и изведене класе. На пример, лако
можемо да замислимо потребу да формирамо низ правоугаоника и да затим нешто израчунамо на основу
координата њихових темена. Неки од тих правоугаоника могу да буду поравнати са координатним осама,
а неки ротирани у односу на осе. То значи да у низу могу да се појаве и објекти класе
Pravougaonik
и они класе RotiraniPravougaonik
. Према томе, низ би био декларисан као низ
референци на базну класу, а те референце могу да указују на објекте било базне, било изведене класе.
int n = int.Parse(Console.ReadLine());
Pravougaonik[] a = new Pravougaonik[n];
...
Поменути механизам виртуелних метода нам омогућава да поравнате и ротиране правоугаонике смештене у истом низу користимо не обраћајући пажњу на то који је ког типа.
Виртуелни методи су још један начин постизања динамичког полиморфизма, који смо већ остваривали помоћу апстрактних метода.
Употреба виртуелних метода¶
За методе и својства код којих желимо понашање у складу са стварним типом актуелног објекта, у
базној класи треба да напишемо реч virtual
испред имена метода, односно својства. Тиме и даље
омогућавамо изведеним класама да користе метод или својство базне класе такве какви су, ако им то
одговара. Другим речима, виртуелан метод је подразумевано понашање и оно ће бити примењено ако
изведена класа не зада другачије понашање.
Са друге стране, изведеним класама остављамо могућност и да редефинишу ове (виртуелне) методе на
такав начин, да се у претходно описаним ситуацијама при позиву користи тај редефинисани метод.
Када је метод у базној класи означен као виртуелан, у изведеној класи га редефинишемо тако што
испред имплементације истоименог метода додамо реч override
.
Ево како можемо да преправимо пример са правоугаоницима, да би се при употреби објеката преко референци на базну класу добило понашање у складу са стварним типом објекта.
// popravljena klasa Pravougaonik
public class Pravougaonik
{
protected double w, h;
protected double ax, ay;
public Pravougaonik(double w, double h, double ax, double ay)
{
this.w = w;
this.h = h;
this.ax = ax;
this.ay = ay;
}
public double Obim() { return 2 * w + 2 * h; }
public double Povrsina() { return w * h; }
public double W { get { return w; } }
public double H { get { return h; } }
virtual public double AX { get { return ax; } }
virtual public double AY { get { return ay; } }
virtual public double BX { get { return ax + w; } }
virtual public double BY { get { return ay; } }
virtual public double CX { get { return ax + w; } }
virtual public double CY { get { return ay + h; } }
virtual public double DX { get { return ax; } }
virtual public double DY { get { return ay + h; } }
public override string ToString()
{
string f = "[({0:0.00}, {1:0.00}), ({2:0.00}, {3:0.00}), "
+ "({4:0.00}, {5:0.00}), ({6:0.00}, {7:0.00})]";
return string.Format(f, AX, AY, BX, BY, CX, CY, DX, DY);
}
}
// popravljena klasa RotiraniPravougaonik
public class RotiraniPravougaonik : Pravougaonik
{
private double sinUgla;
private double cosUgla;
public RotiraniPravougaonik(double a, double b,
double ax, double ay, double ugao)
: base(a, b, ax, ay)
{
this.sinUgla = Math.Sin(ugao);
this.cosUgla = Math.Cos(ugao);
}
override public double BX { get { return ax + w * cosUgla; } }
override public double BY { get { return ay + w * sinUgla; } }
override public double CX { get { return BX - h * sinUgla; } }
override public double CY { get { return BY + h * cosUgla; } }
override public double DX { get { return ax - h * sinUgla; } }
override public double DY { get { return ay + h * cosUgla; } }
}
Извршимо следећих неколико наредби са овако написаним класама.
Pravougaonik p2;
Console.WriteLine("Da li da naprvim obican ili rotirani pravougaonik?");
string odgovor = Console.ReadLine();
if (odgovor == "obican")
p2 = new Pravougaonik(1, 1, 0, 0);
else
p2 = new RotiraniPravougaonik(5, 5, 0, 0, Math.PI/4);
Console.WriteLine("Tacka B je B({0:0.00}, {1:0.00}), ", p2.BX, p2.BY);
Console.WriteLine(p2);
Уношењем различитих одговора можемо да се уверимо да се овај пут добија исправан резултат и када је
објекат типа Pravougaonik
, као и када је типа RotiraniPravougaonik
. Исто важи и за текстуалну
репрезентацију објеката, тј. имплицитну употребу метода ToString
у последњој наредби, где се сада
такође узима у обзир стваран тип објекта. Другим речима, постигли смо исправно, полиморфно понашање
правоугаоника (динамички полиморфизам).
Како се остварује механизам виртуелних метода
Ако сте читали ранија детаљнија објашњења у вези са семантиком, можда сте се запитали како компајлер може да узме у обзир стваран тип објекта када у време компајлирања тај тип није познат. Мада је одговор на ово питање ван оквира курса, дајемо поједностављено објашњење за посебно заинтересоване.
Компјалер за сваку класу може да одреди који њени методи су виртуелни. Такође, за сваки виртуелан метод дате класе компајлер може да одреди где је дефинисан метод који важи за објекте те класе. То може да буде у самој класи, или у некој од претходних (базних) класа у ланцу наслеђивања. На основу тих информација, компајлер може да за сваку класу направи тзв. табелу виртуелних метода (virtual method table), у којој за сваки виртуелан метод пише где се у меморији налази извршиви кôд тог метода.
Након формирања табела виртуелних метода за сваку класу, довољно је да се у сваки објекат приликом његовог креирања угради и референца на одговарајућу табелу виртуелних метода, која важи за његову класу.
Сада позив метода преко референце на базну класу може да се разреши на следећи начин:
За виртуелне методе компајлер уграђује кôд за поглед у виртуелну табелу и проналажење у тој табели адресе одговарајућег метода који ће бити позван.
За методе који нису виртуелни, компајлер и даље може да угради у кôд директан позив метода са конкретном адресом, било да је реч о наслеђеном, или новом методу. Међутим, компајлери понекад не користе ту могућност јер је нешто сложенија за имплементацију, већ у случају да је бар један метод виртуелан, свим методима уграђују приступ преко табеле.
Овим поступком се омогућава да одлука о избору метода који ће бити позван буде одложена до извршавања програма, уместо да се та одлука доноси у време превођења. Пошто се адреса виртуелног метода дохвата посредно, преко поменуте табеле, покретање виртуелних метода је спорије него покретање обичних метода. Ово треба имати на уму приликом писања програма у којима перформансе имају изразити приоритет у односу на друге квалитете програма.