fel
le

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.

Hernyák Zoltán
2013-03-17 19:17:18