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!