Személyes eszközök
Keresés

 

A InfoWiki wikiből


Kötés

Kötés-nek nevezzük azt a folyamatot, amikor a fordítóprogram a forráskód fordítása során felfedezett metódushívásokhoz konkrét metódust rendel.

A kötés során döntést kell hoznia, hogy melyik metódushívás melyik metódust jelentse. Ennek szabályai vannak:

  • Overloading szabály: a hívás helyén feltüntetett aktuális paraméterlista melyik formális paraméterlistára illeszkedik.
  • Statikus típus szabály: amely példányon keresztül próbáljuk meghívni a metódust, annak a deklarált típusa határozza meg a kiválasztott metódust.

Példa az overloading-ra:

class TAuto
{
   public void Balkanyar()  { ... }
}
 
class TAuto2:TAuto
{
   public void Balkanyar(double szog)  { ... }
}
 
class FoProgram
{
   public static void Main()
   {
      TAuto2 kocsi = new TAuto2();
      kocsi.Balkanyar(); 
      kocsi.Balkanyar(45);
   }
}

A fenti példában az első 'Balkanyar()' hívás egyértelműen a 'TAuto'-ban definiált metódust jelent, míg a második, paraméteres 'Balkanyar(45)' pedig a 'TAuto2'-ben definiált metódust jelöli.


Példa a statikus típus szabályra:

class TRepulo
{
   public void Felszall()  { ... }
}
 
class THelikopter:TRepulo
{
   new public void Felszall() { ... }
}
 
class FoProgram
{
   public static void Main()
   {
      TRepulo f15 = new TRepulo();
      f15.Felszall(); 
      THelikopter apache = new THelikopter();
      apache.Felszall();
   }
}

Az első esetben az 'f15' példány típusa 'TRepulo', ezért a rajta keresztül meghívott metódus esetén a fordító megvizsgálja, hogy mely metódusok jöhetnek szóba. A TRepulo osztályban definiált 'Felszall()' metódus nyilván szóba jöhet, míg a helikopter-é nem. A második esetben, az 'apache' esetén pedig nyilván a 'new'-val felüldefiniált új, helikopteres felszálló metódus kerül meghívásra.

Korai kötés

A kötés során metódushívást rendelünk metódushoz.

A korai kötés-ről akkor beszélhetünk, ha ez az összerendelés fordítási időben történik, a fordítóprogram által.

A korai kötés legfontosabb jellemzője, hogy oldhatatlan, megváltoztathatatlan kötés. A programozók egymás között szoktak úgy is fogalmazni: "beheggesztődik a kódba". Vagyis ha a fordítóprogram egyik kötés alkalmával meghozott egy konkrét döntést, akkor azt később m ár sosem fogja felülbírálni, megváltoztatni:

class TRepulo
{
   public void Felszall()  { ... }
   public void Leszall()   { ... }
   public void Repul()     { ... }
 
   public void Gyakorlokor()
   {
     Felszall();
     Repul();
     Leszall();
   }
}
 
class THelikopter:TRepulo
{
   new public void Felszall() { ... }
   new public void Leszall() { ... }
}
 
class FoProgram
{
   public static void Main()
   {
      TRepulo f15 = new TRepulo();
      f15.Gyakorlokor(); 
      THelikopter apache = new THelikopter();
      apache.Gyakorlokor();
   }
}

Gondoljuk végig, mely metódusok hívódnak meg az 'f15.Gyakorlokor()' és az 'apache.Gyakorlokor()' esetén!

A fordítóprogram fordítás a 'TRepulo' osztály fordítása közben amikor a 'Gyakorlokor()' metódus törzsét fordítja, ott metódushívásokat talál. Ezeket megpróbálja kötni valamely konkrét metódusokhoz. Mivel jelenleg a TRepulo objektumosztály fordítását végzi, és ebben megtalálja a keresett metódusokat, így ezen metódushívásokat rendre összeköti a TRepulo osztályban megtalálható metódusokkal.

A 'THelikopter' osztály fordítása során kötési feladatokkal nem fog találkozni, hiszen ott csak metódusdefiníciók találhatóak, metódushívások nem.

A főprogram fordítása során kettő metódushívással találkozik, az 'f15' és az 'apache' példányokon keresztül. Az 'f15.Gyakorlokor()' kötése egyértelmű, a statikus szabály szerint ez a 'TRepulo' osztálybeli 'Gyakorlokor()' lesz. Hasonlóan egyértelmű a helyzet az 'apache' esetén is, mivel a 'THelikopter'-ben nem került felüldefiniálásra a 'Gyakorlokor()' metódus, így marad az eredeti, örökölt metódus, a 'TRepulo'-beli 'Gyakorlokor()'.

Vizsgáljuk meg hogyan működik a futó program! Amikor a főprogramban elérjük a 'f15' példány gyakorlókör metódushívását, egyszerűen követni kell a korai kötés döntéseit:

f15.Gyakorlokor()</b> -> TRepulo -beli Gyakorlokor()
{
  Felszall()  -> TRepulo -beli Felszall()
  Repul()     -> TRepulo -beli Repul()
  Leszall()   -> TRepulo -beli Leszall()
}

Ez ideig nem okozott semmi váratlan viselkedést, hiszen az 'f15' példány a 'TRepulo'-bol lett példányosítva, amelynek léteznek 'Felszall()', 'Repul()', 'Leszall()' metódusai, és nyilván ezek kerülnek használatra.

Most ellenőrízzük ugyanezt az 'apache' példány esetén:

apache.Gyakorlokor()</b> -> TRepulo -beli Gyakorlokor()
{
   Felszall()     -> TRepulo -beli Felszall()
   Repul()        -> TRepulo -beli Repul()
   Leszall()      -> TRepulo -beli Leszall()
}

Vagyis azt tapasztaljuk, hogy a 'THelikopter'-ben hiába került 'new' segítségével felüldefiniálásra a 'Felszall()' és 'Leszall()', nem kerültek meghívásra! Ez viszont fontos tanulság, és nem feltétlenül logikus!

Tanulság: a 'new'-al felüldefiniált metódusok működnek, amennyiben direktben hívjuk meg őket a megfelelő típusú példányon keresztül, de az örökölt metódusokból nem lesznek elérhetőek. Az örökölt metódusok belsejében lévő metódushívásokra a korai kötés vonatkozik, amely egyrészt megváltozathatatlan, másrészt nem irányulhat egy későbbi osztály belsejében később definiált metódusok felé.

A 'new'-val történő metódus-felüldefiniálás csak félig nyújt megoldást az objektum-osztályunk továbbfejlesztési problémáira. Az ilyen metódusokat a módosítás nélkül átvett, örökölt metódusok nem fogják felhasználni mint javított változatot. Ezek hiányában az osztályunk csak "félig javított" állapotú, ha egy ilyen örkölt metódust kívánunk meghívni, akkor ez a "felemás viselkedés" előjön, és ez komoly működési zavarokhoz vezethet.

Mi lehet a megoldás?

A korai kötés kezelése

A fenti esetben megoldást nyújthat az alábbi módosítás:

class TRepulo
{
   public void Felszall()  { ... }
   public void Leszall()   { ... }
   public void Repul()     { ... }
 
   public void Gyakorlokor()
   {
     Felszall();
     Repul();
     Leszall();
   }
}
 
class THelikopter:TRepulo
{
   new public void Felszall() { ... }
   new public void Leszall() { ... }
   new public void Gyakorlokor()
   {
     Felszall();
     Repul();
     Leszall();
   }
}
 
class FoProgram
{
   public static void Main()
   {
      TRepulo f15 = new TRepulo();
      f15.Gyakorlokor(); 
      THelikopter apache = new THelikopter();
      apache.Gyakorlokor();
   }
}

Ebben az esetben a 'TRepulo' osztályban feltüntett 'Gyakorlokor()' metódust egyszerűen lemásoltuk a 'THelikopter' belsejében a 'copy-paste' segítségével. Milyen változást eredményez ez?

A 'THelikopter' fordítása során a 'new Gyakorlokor()' metódusban megadott metódushívásokat a fordító megpróbálja kötni. A 'Felszall()'-t a saját osztálybeli 'new Felszall()'-hoz, a 'Repul()'-t jobb híján az örökölt 'Repul()'-höz. A 'Leszall()'-t az új 'new Leszall()'-hoz.

A főprogramban az 'apache.Gyakorlokor()' metódushívás kötése során a fordító ehhez az új 'new Gyakorlokor()' metódushoz fog kötni. Lássuk mi történik futás során:

apache.Gyakorlokor()</b> -> THelikopter -beli Gyakorlokor()
{
  Felszall()     -> THelikopter -beli Felszall()
  Repul()        -> TRepulo -beli Repul()
  Leszall()      -> THelikopter -beli Leszall()
}

Vagyis a kód futás közben úgy működik, ahogy azt mi elvártuk volna. Node milyen áron?

A 'copy-paste' módszerrel történő OOP kód fejlesztés teljesen hibás hozzáállás! Ez a kód duplikálását jelenti, hiszen a Gyakorlokor() metódus belseje a forráskód szerint (a kötésektől eltekintve) megegyezik az örökölt metódus törzsével. Ez pontosan az egyik OOP alapelv, az öröklődés elvének sárba tiprását jelenti! Ezen elv azért került kidolgozásra, hogy a fejlesztést meggyorsítsa, és azt mondja ki, hogy amennyiben egy metóduson nem végzünk módosítást, úgy azt vegyük át változtatás nélkül az őstől.

Gondoljuk ezen kívül végig az alábbiakat:

  • a metódus copy-paste átvétele nem minden esetben kivitelezhető, mivel leggyakrabban az ős osztály forráskódja nem is áll rendelkezésre
  • az eredeti metódus hivatkozhat private mezőkre, vagy hívhat private metódusokat. Ezt a copy-paste kód nem képes megtenni a gyermekosztályban
  • amennyiben lemásolnánk a metódust magunkhoz, úgy elvesztenénk a kapcsolatot az ős osztállyal ezen a vonalon. Ha az ős osztályban javítanak ezen a metóduson, akkor vagy újra meg kell ismételnünk a copy-paste műveletet, vagy beletörődünk, hogy a mi felüldefiniált metódusunk marad a régi, kicsit hibás.

Nyilván a fentiek egyike sem engedhető meg, ha jó minőségű kódot szeretnénk írni. Olyan megoldást kell keresni, amely megoldja a fenti problémákat!

Hernyák Zoltán
A lap eredeti címe: „http://wiki.ektf.hu/wiki/Mp3/ea07
Nézetek
nincs sb_3.17.28.48 cikk