A InfoWiki wikiből
WikiSysop (vita | szerkesztései)
(Új oldal, tartalma: „<cim cim3="Késői kötés" cim2="Objektum-orientált programozás" cim1="Magasszintű Programozási Nyelvek II." prev="mp3/ea07" next="mp3/ea09" back="mp3/Nyi...”)
Újabb szerkesztés →
Aktuális változat
Tartalomjegyzék |
Késői kötés
Az előző fejezet végén felvetett problémára nincs jó megoldás. Amennyiben az ős osztályban van olyan metódus, amely más metódusokat meghív, akkor korai kötés mellett ezek a hívások sosem fogják a gyerekosztályban esetleg újradefiniált, új verziójú metódusokat használni még akkor sem, ha ezen gyermekosztályból készítünk példányokat.
A problémát úgy is fel lehet fogni, hogy nem figyelmeztettük a fordítóprogramot arra, hogy a szóban forgó metódusokból később újabb verziók is keletkezhetnek. Ennek következtében a fordító a korai kötést használja, amely nem elég rugalmas a gyermekosztálybeli továbbfejlesztések kezelését illetően.
Jelzést kell adnunk a fordítóprogramnak, hogy valamely metódust ilyen típusúnak kezelje. Az alapértelmezett kezelés a korai kötés, mint az eddigi példák mutatták. A másik kezelési technika a késői kötés, de ennek alkalmazásához a metódust meg kell jelölni.
Virtuális metódusok
Amennyiben olyan metódus írásába kezdünk, amely
- a gyermekosztályokban valószínűleg felülírásra kerül
- más metódusok által meghívásra kerül, és van esélye hogy a gyermekosztályokban felülírásra kerül
ezeket már az ősosztályban is meg kell jelölni a virtual kulcsszóval.
class TRepulo { virtual public void Felszall() { ... } virtual public void Leszall() { ... } public void Repul() { ... } public void Gyakorlokor() { Felszall(); Repul(); Leszall(); } }
Amennyiben a fordítóprogram ilyen kulcsszóval megjelölt metódus hívására bukkan, akkor nem korai kötéssel oldja fel a metódus-hozzárendelést, hanem ezt a problémát "elhalasztja". Egy olyan kódot generál a metódushívás helyére, amely majd csak futás közben, amikor konkrétan ezt a pontot éri el a program, csak akkor dönti el, hogy melyik konkrét metódus hívódjon meg.
Az ilyen metódusokat a gyermekosztályban jogunk van felüldefiniálni, de nem alkalmazhatjuk a 'new' kulcsszót. Ha mégis megpróbálnánk, akkor a fordítóprogram hibát jelezne, mivel a 'new' kulcsszó a korai kötést is jelöli egyben. Helyette az 'override' kulcsszót kell használni:
class THelikopter:TRepulo { override public void Felszall() { ... } override public void Leszall() { ... } }
A fenti kód fordítása során a 'Gyakorlokor()' metódus belseje a legérdekesebb:
{ Felszall() -> ?? késői kötés ?? Repul() -> TRepulo -beli Repul() Leszall() -> ?? késői kötés ?? }
Működés közben
Az alábbi kód próbálja használni a fenti osztályokat:
class FoProgram { public static void Main() { TRepulo f15 = new TRepulo(); f15.Gyakorlokor(); THelikopter apache = new THelikopter(); apache.Gyakorlokor(); } }
Az 'f15' példány esetén az alábbi történik:
f15.Gyakorlokor() -> TRepulo -beli Gyakorlokor() { Felszall() -> ?? késői kötés ?? -> f15 típusa szerint -> TRepulo -beli Felszall() Repul() -> TRepulo -beli Repul() Leszall() -> ?? késői kötés ?? -> f15 típusa szerint -> TRepulo -beli Leszall() }
Az 'apache' esetén pedig:
apache.Gyakorlokor() -> TRepulo -beli Gyakorlokor() { Felszall() -> ?? késői kötés ?? -> apache típusa szerint -> THelikopter -beli Felszall() Repul() -> TRepulo -beli Repul() Leszall() -> ?? késői kötés ?? -> apache típusa szerint -> THelikopter -beli Leszall() }
A fenti két esetet alaposabban megvizsgálva azt tapasztalhatjuk, hogy a TRepulo-beli metódushívások alkalmazkodnak az adott futásközbeni szituációhoz - a példány típusa szerinti legújabb metódus-változatok hívódnak meg. Ezt a tulajdonságát az O.O.P.-nek hívjuk sokalakúság-nak.
Szabályok
Amennyiben az ős osztályban egy metódust virtual-nak deklaráltunk, úgy a gyermek osztályban
- nem használhatjuk a 'new' kulcsszót a felüldefiniálásra
- kötelező használni az override kulcsszót
Ha egy virtuális metódust szeretnénk 'override' segítségével felüldefiniálni, úgy
- nem változtathatjuk meg annak nevét, hiszen név alapján fogja azonosítani a fordító, melyik virtuális metódust is kívánjuk felüldefiniálni
- nem változtathatjuk meg ezen metódus paraméterezését, hiszen az ős osztályból a régi paraméterezéssel fogjuk ezen új változatot is meghívni
- amennyiben a metódus függvény, úgy nem változtathatjuk meg annak visszatérési típusát sem
Összefoglalóan tehát azt mondhatjuk el, hogy az 'override' metódusváltozatban semmit sem változtathatunk meg, csak annak törzsét.
Mivel a virtuális metódusok értelme, hogy azokat a gyerekosztályok felüldefiniálják, így a virtuális metódusok hatásköre nem lehet 'private'.
Virtuális property
Hasonlóan a metódusokhoz, property-k is lehetnek virtuálisak.
class Ember { protected int _eletkor; public virtual int eletkor { get { return _eletkor; } set { if (value>=0) _eletkor = value; else throw new Exception("Hibas ertek!"); } } }
A gyermekosztályban jogunk van a property akár 'get' akár 'set', akár mindkét részét módosítani. Ha csak az egyik felét módosítjuk (pl. csak a 'get' részt), akkor a másik rész esetén az örökölt kerül be a kódba:
class GyermekEmber:Ember { public override int eletkor { set { if (value>=0 && value<18) _eletkor = value; else throw new Exception("Hibas ertek!"); } } }
Amennyiben a property-t bevezető ős osztály nem dolgozta ki mindkét részt (get és set), úgy a gyermekosztály sem dolgozhatja ki a hiányzó részt!
class Computer { public virtual string IP_Cime { get { return Operating_System.IP_Address; } } } // --- class AdvComputer { public override string IP_Cime { // HIBA: nem dolgozhat ki SET részt !!! set { Operating_System.IP_Address = value; } } }
Állítólag igaz történet
Objektum-orientált kód újrafelhasználása miatt érte egy kis kellemetlenség az ausztrál hadsereget.
Manapság ugye már egyre jobb helikopter-szimulátorokat csinálnak, amelyek szinte teljesen ugyanazt az élményt nyújtják, mint az igazi repülés. Domborzat, időjárás, növényzet: mind teljesen élethű. Az ausztrálok úgy gondolták, hogy az állatokat is be kellene rakni, mivel azok a menekülésükkel információt szolgáltathatnak az ellenségnek a környéken repülő helikopterről, például oly módon, hogy a zajtól csoportba verődő, nagy tömegben menekülő állatfalka futás közben port ver fel, ez messzire látszik, és elárulhatja az amúgy alacsonyan repülő helikopterek helyzetét.
A kutatás-fejlesztés főnöke üzent a programozóknak, hogy tegyenek a programba néhány kenguru-falkát is. A programozók öreg rókaként persze nem kezdtek vadul kódot írni, hanem elővettek egy már meglévő objektum-osztályt: a gyalogságot.
A menekülési algoritmus ugyanaz maradt, mindössze a bitmap képeket kellett lecserélni, a futás sebességét megnövelni, a mozgást ugrálóra alakítani és már készen is volt a kenguru-csapat.
Történt aztán egyszer, hogy amerikai katonák jöttek látogatóba az ausztrálokhoz. A helyi nagyfiúk persze egyből vakítani kezdtek az amerikaiaknak: mélyrepülésben húztak a nagy kenguru-nyáj felé, mire azok jól szétspricceltek. Az amerikai katonák elismerően bólogattak a mutatvány láttán... aztán döbbentek egy nagyot, amikor az egyik bokor mögül kinézett egy kenguru, majd előrántott egy méretes RPG rakétavetőt, vállára kapta, rövid célzás után kilőtte az egyik helikoptert.
A programozók ugyanis elfelejtették kivenni ezt a részt a kódból (az összes metódus öröklődött, köztük az 'ellenségesTámadásraReagálás(...)' rész is :).
A tanulság az ausztrál pogramozók számára az lett, hogy óvatosan kell bánni a kódok újrafelhasználásával, az amerikaiak számára pedig az, hogy az ausztrál vadvilág tényleg olyan veszélyes, mint ahogy azt beszélik.
A nagyfőnökök egyébként örültek az esetnek, mert a pilóták megtanulták a leckét: azóta mindegyikük szigorúan elkerüli a kengurukat.