Többszörös öröklődés
A többszörös öröklődés olyan funkció néhány objektumorientált programozási nyelvben, amely lehetővé teszi, hogy egy objektum vagy osztály több szülőobjektumtól vagy szülőosztálytól örökölje a tulajdonságokat. Ez különbözik az egyszeres öröklődéstől, ahol egy objektum vagy osztály csak egyetlen meghatározott objektumtól vagy osztálytól örökölhet.
A többszörös öröklődés évek óta vitatott kérdés,[1][2] mivel az ellenzők rámutatnak a megnövekedett komplexitásra és a kétértelműségekre, például a "gyémánt probléma" esetében, amikor nem egyértelmű, hogy melyik szülőosztályból öröklődik egy adott tulajdonság, ha több szülőosztály is megvalósítja azt. Ezt különböző módokon lehet kezelni, például virtuális öröklődéssel.[3] Az öröklődésen alapuló objektum összetétel alternatív módszereit, mint például a mixinek és a trait-ek, szintén javasolták a kétértelműségek kezelésére.
Részletek
[szerkesztés]Az objektumorientált programozásban (OOP) az öröklődés két osztály közötti kapcsolatot ír le, amelyben az egyik osztály (a gyermekosztály ) alosztálya a szülőosztálynak. A gyerek örökli a szülő metódusait és attribútumait, lehetővé téve a közös funkcionalitást. Például létrehozhatunk egy Emlős nevű változó osztályt olyan tulajdonságokkal, mint az evés, szaporodás stb.; majd definiálhatunk egy Macska nevű gyerekosztályt, amely örökli ezeket a tulajdonságokat anélkül, hogy explicit módon kellene azokat megprogramozni, miközben új tulajdonságokat is hozzáad, mint például az egerek üldözése.
A többszörös öröklődés lehetővé teszi a programozók számára, hogy egynél több teljesen ortogonális hierarchiát használjanak egyszerre, például lehetővé teszi, hogy a Macska örökölje a rajzfilmfiguráktól, a Háziállatot és az Emlősöket, és az összes ilyen osztályból elérje a funkciókat.
Megvalósítások
[szerkesztés]A többszörös öröklődést támogató nyelvek a következők: C++, Common Lisp, EuLisp, Curl, Dylan, Eiffel, Logtalk, Object REXX, Scala, OCaml, Perl, POP-11, Python, R, Raku és Tcl.[4][5]
Az IBM rendszerobjektum modell (SOM: IBM System Object Model) futási utási ideje támogatja a többszörös öröklést, és bármely SOM-ot megcélzó programozási nyelv képes több bázisból örökölt új SOM-osztályok megvalósítására.
Néhány objektumorientált nyelv, például a Swift, a Java, a Fortran 2003-as verziója óta, a C# és a Ruby egyszeres öröklődést valósítanak meg, bár a protokollok vagy interfészek a valódi többszörös öröklődés néhány funkcióját biztosítják.
A PHP tulajdonság osztályokat használ bizonyos metódusmegvalósítások öröklésére. A Ruby modulokat használ több metódus öröklésére.
A gyémánt probléma
[szerkesztés]A "gyémánt probléma" (néha "halálos gyémánt" (angolul: "Deadly Diamond of Death"[6]) néven is ismert) egy kétértelműség, amely akkor merül fel, amikor két osztály, B és C, örököl A-ból, és a D osztály mind B-ből, mind C-ből örököl. Ha van egy metódus A-ban, amelyet B és C felülírt, de D nem írja felül azt, akkor melyik verzióját örökli D: B-ét vagy C-ét?
Például a GUI szoftverfejlesztés kontextusában egy Button
osztály örökölhet mind a Rectangle
osztályból (megjelenés miatt), mind a Clickable
osztályból (funkcionalitás/bemenet kezelés miatt), és a Rectangle
és a Clickable
osztályok mind az Object
osztályból örökölnek. Most, ha az equals
metódust hívják meg egy Button
objektum esetében, és nincs ilyen metódus a Button
osztályban, de van felülírt equals
metódus a Rectangle
vagy a Clickable
osztályban (vagy mindkettőben), akkor melyik metódust kell végül meghívni?
Ezt "gyémánt problémának" nevezik, mert az osztályöröklési diagram alakja ilyen helyzetben gyémánt formájú. Ebben az esetben az A osztály van a tetején, alatta külön-külön a B és C osztályok, majd D alul egyesíti a kettőt, így gyémánt alakot formálva.
Enyhítés
[szerkesztés]A nyelveknek eltérő módszereik vannak a megismételt öröklődés problémáinak kezelésére.
- A C# (C# 8.0-tól kezdve) lehetővé teszi az alapértelmezett interfész metódusmegvalósítást, ami azt eredményezi, hogy egy
A
osztály, amely implementálja azIa
ésIb
interfészeket hasonló metódusokkal, amelyek alapértelmezett megvalósítással rendelkeznek, két "örökölt" metódussal rendelkezhet ugyanazzal a szignatúrával, és ezzel a gyémánt problémát okozza. Ezt vagy azáltal lehet enyhíteni, hogy azA
-t kötelezővé tesszük a metódus saját implementálására, ezáltal elkerülve az egyértelműséget, vagy kényszerítjük a hívót arra, hogy először átkonvertálja azA
objektumot a megfelelő interfészre annak a metódusnak az alapértelmezett implementációjának használatához (pl.((Ia) aInstance).Method();
. - A C++ alapértelmezetten minden öröklési utat külön követ, tehát egy
D
objektum valójában két különA
objektumot tartalmaz, és azA
tagok használata megfelelően ki kell egészíteni. Ha az öröklésA
-tólB
-ig és az öröklésA
-tólC
-ig mindkettő "virtual
" (például "class B : virtual public A
"), akkor a C++ különleges figyelmet fordít arra, hogy csak egyA
objektumot hozzon létre, és azA
tagok használata helyesen működik. Ha a virtuális öröklést és a nem virtuális öröklést összekeverik, akkor egyetlen virtuálisA
van, és egy nem virtuálisA
van minden nem virtuális öröklési úton azA
-hoz. A C++-ban ki kell egyértelműen jelölni, hogy melyik szülőosztályból hívják meg az adott tulajdonságot, azazWorker::Human.Age
. A C++ nem támogatja az explicit ismétlődő öröklést, mivel nem lenne mód arra, hogy melyik szuperosztályt kell használni (például egy osztály többször is megjelenhet egyetlen származási listában [class Dog : public Animal, Animal]). A C++ továbbá lehetővé teszi a többszörös osztály egyetlen példányának létrehozását a virtuális öröklés mechanizmusán keresztül (példáulWorker::Human
ésMusician::Human
ugyanarra az objektumra fog hivatkozni). - A Common Lisp CLOS megpróbálja biztosítani mind a megfelelő alapértelmezett viselkedést, mind annak felülbírálásának lehetőségét. Alapértelmezetten egyszerűen fogalmazva a módszerek
D,B,C,A
, sorrendben vannak rendezve, amikor B előbb szerepel a C osztálydefinícióban. A legspecifikusabb argumentumosztályokkal rendelkező módszer lesz kiválasztva (D>(B,C)>A); majd a szülőosztályok sorrendjében, ahogyan azokat a részosztály definíciójában megnevezték (B>C). Azonban a programozó felülírhatja ezt, megadva egy konkrét módszerfeloldási sorrendet vagy szabályt a módszerek kombinálására. Ezt metódus kombinációnak nevezik, amely teljes mértékben ellenőrizhető. A MOP (metatárgy protokoll) továbbá lehetőséget biztosít az öröklődés, a dinamikus kiküldés, az osztály példányosítása és más belső mechanizmusok módosítására anélkül, hogy az rendszer stabilitását befolyásolná. - A Curl csak azokat az osztályokat engedi, amelyeket egyértelműen megjelöltek közöseknek, hogy ismétlődően öröklődhessenek. A közös osztályoknak minden rendes konstruktorhoz meg kell határozniuk egy másodlagos konstruktort az osztályban. A rendes konstruktort akkor hívják meg először, amikor a közös osztály állapota egy részosztály konstruktorán keresztül inicializálódik, és a másodlagos konstruktor minden más részosztályhoz lesz hívva.
- Az Eiffelben az ősosztályok tulajdonságai explicit módon választhatók ki a "select" és "rename" direktívákkal. Ez lehetővé teszi az alap osztály tulajdonságainak megosztását az örököseik között, vagy mindegyiknek külön példányt ad a bázis osztályból. Az Eiffel lehetővé teszi az örökölt tulajdonságok explicit összekapcsolását vagy szétválasztását is. Az Eiffel automatikusan összekapcsolja a tulajdonságokat, ha azok azonos nevet és megvalósítást kapnak. Az osztály író választhatja meg az örökölt tulajdonságok átnevezését azok szétválasztásához. A többszörös öröklődés gyakori jelenség az Eiffel fejlesztésben; például a legtöbb hatékony osztály a széles körben használt EiffelBase adatszerkezetek és algoritmusok könyvtárában két vagy több szülőt használ.[7]
- A Go a fordítási időben megakadályozza a gyémánt problémát. Ha egy
D
struktúra kétB
ésC
struktúrát ágyaz be, amelyek mindegyike rendelkezik egyF()
nevű módszerrel, és ezzel kielégíti azA
interfészt, akkor a fordító "kétértelmű szelektor" hibával fog panaszkodni, haD.F()
hívódik meg, vagy ha egyD
példányt egy A típusú változóhoz rendelnek.A
.B
ésC
módszerei explicit módon hívhatók aD.B.F()
vagy aD.C.F()
módon. - A Java 8 bevezeti az alapértelmezett metódusokat az interfészekben. Ha
A,B
ésC
interfészek, akkorB
ésC
mindegyike különböző megvalósítást adhat egyA
absztrakt módszeréhez, ami a gyémánt problémát okozza. Vagy aD
osztálynak újra kell megvalósítania a módszert (amelynek törzsét egyszerűen átirányíthatja a hívást az egyik szülő megvalósítására), vagy az egyértelműséget fordítási hibaként elutasítják.[8] A Java 8 előtt a Java nem volt kitéve a gyémánt probléma kockázatának, mert nem támogatta a többszörös öröklődést, és az interfész alapértelmezett metódusai nem voltak elérhetők. - A JavaFX Script 1.2 verziója mixinek használatával teszi lehetővé a többszörös öröklődést. Konfliktus esetén a fordító megtiltja a kétértelmű változók vagy függvények közvetlen használatát. Minden örökölt tagot továbbra is elérhetünk az objektum átalakításával az érdeklődési mixin-re, például
(individual as Person).printInfo();
. - A Kotlin lehetővé teszi az interfészek többszörös öröklését. Azonban egy gyémánt probléma forgatókönyvében a gyerekosztálynak felül kell írnia azt a módszert, amely okozza az öröklési konfliktust, és meg kell adnia, hogy melyik szülőosztály implementációját kell használni. például
super<ChosenParentInterface>.someMethod()
- A Logtalk támogatja mind az interfész, mind az implementáció többszörös öröklődését, lehetővé téve a módszernelnevek deklarálását, amelyek mind az átnevezést, mind a módszerek hozzáférhetőségét biztosítják, és elkerülik az alapértelmezett konfliktusfeloldási mechanizmus által elrejtett módszereket.
- Az OCaml-ben a szülőosztályokat külön-külön határozzák meg az osztálydefiníció testében. A módszerek (és attribútumok) ugyanabban a sorrendben öröklődnek, és minden újonnan örökölt módszer felülírja a meglévő módszereket. Az OCaml az osztályöröklési lista utolsó egyező definícióját választja ki a módszer megvalósításának eldöntéséhez a kétértelműségek esetén. Az alapértelmezett viselkedés felülbírálásához egyszerűen meg kell adni a kívánt osztálydefiníciót a módszerhívás során.
- A Perl a szülőosztályok listáját rendezett listaként használja az öröklődéshez. A fordító az első módszert használja, amelyet a szuperosztályok listájának mélységi keresésével talál meg, vagy a osztály hierarchiájának C3 linearizációját használva. Különböző kiterjesztések alternatív osztálykompozíciós sémaákat biztosítanak. Az öröklés sorrendje befolyásolja az osztály szemantikáját. A fenti kétértelműség esetén a
B
osztály és annak ősai ellenőrizve lesznek aC
osztály és annak ősai előtt, így azA
módszerB
-n keresztül öröködik. Ez megosztott Io és Picolisppel. A Perlben ezt a viselkedést felül lehet írni a mro vagy más modulok használatával a C3 linearizáció vagy más algoritmusok használatával.[9] - A Python ugyanazt a struktúrát használja, mint a Perl, de ellentétben a Perllal, belefoglalja a nyelv szintaxisába. Az öröklés sorrendje befolyásolja az osztály szemantikáját. A Pythonnak meg kellett küzdenie ezzel a problémával az új stílusú osztályok bevezetésekor, amelyek mindegyikének van egy közös ősük, az object. A Python egy osztálylistát hoz létre a C3 linearizációs (vagy MRO - Method Resolution Order) algoritmus segítségével. Ez az algoritmus két kényszer betartását erőszakolja: a gyerekek előzik meg a szülőket, és ha egy osztály több osztályból örököl, azokat az osztályokat tartják meg a base classes tuple-ban megadott sorrendben (azonban ebben az esetben néhány osztály a hierarchiában magasabb helyen lehet, mint az alacsonyabbak[10]). Tehát a módszer feloldási sorrendje:
D
,B
,C
,A
.[11] - A Ruby osztályoknak pontosan egy szülőjük van, de több modulból is örökölhetnek; a Ruby osztálydefiníciók végrehajtódnak, és egy módszer (újra)definiálása elrejti az összes korábbi meghatározást a végrehajtás idején. A futásidejű metaprogramozás hiányában ez közel azonos szemantikával rendelkezik, mint a jobboldali mélységi keresés.
- A Scala lehetővé teszi a trait-ek többszörös beállítását, ami lehetővé teszi a többszörös öröklődést a osztály- és trait-hierarchia közötti megkülönböztetés hozzáadásával. Egy osztály csak egy osztályból örökölhet, de annyi traitet keverhet be, amennyit csak szeretne. A Scala a módszerek neveinek feloldását a kiterjesztett "trait"-ek jobb-bal mélységi keresésével oldja fel, mielőtt az eredményül kapott listában minden modul kivételével az utolsó előfordulást eltávolítaná. Tehát a feloldási sorrend: [
D
,C
,A
,B
,A
], ami lecsökken [D
,C
,B
,A
] -re. - A Tcl több szülőosztályt is engedélyez; a szülőosztályok sorrendje az osztálydeklarációban befolyásolja a tagok nevének feloldását a C3 linearizációs algoritmus használatával.[12]
Azok a nyelvek, amelyek csak egyetlen öröklődést engedélyeznek, ahol egy osztály csak egy alap osztályból származhat, nem rendelkeznek gyémánt problémával. Ennek az az oka, hogy az ilyen nyelvekben legfeljebb egy implementáció van bármelyik módszerre bármely szinten az öröklési láncban, függetlenül a módszerek ismétlődésétől vagy helyétől. Általában ezek a nyelvek lehetővé teszik az osztályok számára, hogy több protokollt, úgynevezett interfészeket implementáljanak, például a Java-ban. Ezek a protokollok módszereket definiálnak, de nem biztosítanak konkrét implementációkat. Ezt a stratégiát használta az ActionScript, a C#, a D, a Java, a Nemerle, az Object Pascal, az Objective-C, a Smalltalk, a Swift és a PHP.[13] Ezek a nyelvek mind engedélyezik az osztályoknak, hogy több protokollt implementáljanak.
Továbbá, az Ada, a C#, a Java, az Object Pascal, az Objective-C, a Swift és a PHP lehetővé teszi az interfészek többszörös öröklődését (amit protokolloknak neveznek az Objective-C-ben és a Swiftben). Az interfészek olyan absztrakt alap osztályokhoz hasonlóak, amelyek módszer aláírásokat határoznak meg anélkül, hogy bármilyen viselkedést megvalósítanának. ("Tiszta" interfészek, mint például azok a Java-ban egészen a 7. verzióig, nem engedélyezik az implementációt vagy az példányadatokat az interfészen.) Mindazonáltal, amikor több interfész deklarálja ugyanazt a módszer aláírást, amint a módszert valahol az öröklési láncban megvalósítják (definiálják), ez felülbírálja a fentebb az láncban lévő bármely módszer implementációját (superclass-ekben). Ezért az öröklési lánc bármely adott szintjén legfeljebb egy módszer implementációja lehet. Így a gyémánt probléma nem jelentkezik az egyetlen öröklési módszer implementációjában, még akkor sem, ha többszörös interfészöröklődés van. A Java 8 és a C# 8 interfészekhez alapértelmezett implementáció bevezetésével még mindig lehetséges a Gyémánt Probléma generálása, bár ez csak fordítási időben jelentkezik hibaként.
Jegyzetek
[szerkesztés]- ↑ Cargill (Winter 1991). „Controversy: The Case Against Multiple Inheritance in C++”. Computing Systems 4, 69–82. o.
- ↑ Waldo (Spring 1991). „Controversy: The Case For Multiple Inheritance in C++”. Computing Systems 4, 157–171. o.
- ↑ Schärli: Traits: Composable Units of Behavior (PDF). Web.cecs.pdx.edu. (Hozzáférés: 2016. október 21.)
- ↑ incr Tcl. blog.tcl.tk. (Hozzáférés: 2020. április 14.)
- ↑ Introduction to the Tcl Programming Language. www2.lib.uchicago.edu. (Hozzáférés: 2020. április 14.)
- ↑ Martin, Robert C.: Java and C++: A critical comparison (PDF). Objectmentor.com , 1997. március 9. [2005. október 24-i dátummal az eredetiből archiválva]. (Hozzáférés: 2016. október 21.)
- ↑ Standard ECMA-367. Ecma-international.org. (Hozzáférés: 2016. október 21.)
- ↑ State of the Lambda. Cr.openjdk.java.net . (Hozzáférés: 2016. október 21.)
- ↑ perlobj. perldoc.perl.org. (Hozzáférés: 2016. október 21.)
- ↑ Abstract: The Python 2.3 Method Resolution Order. Python.org . (Hozzáférés: 2016. október 21.)
- ↑ Unifying types and classes in Python 2.2. Python.org. (Hozzáférés: 2016. október 21.)
- ↑ Manpage of class. Tcl.tk, 1999. november 16. (Hozzáférés: 2016. október 21.)
- ↑ Object Interfaces - Manual. PHP.net , 2007. július 4. (Hozzáférés: 2016. október 21.)
Fordítás
[szerkesztés]Ez a szócikk részben vagy egészben a Multiple inheritance című angol Wikipédia-szócikk ezen változatának fordításán alapul. Az eredeti cikk szerkesztőit annak laptörténete sorolja fel. Ez a jelzés csupán a megfogalmazás eredetét és a szerzői jogokat jelzi, nem szolgál a cikkben szereplő információk forrásmegjelöléseként.
További irodalom
[szerkesztés]- Stroustrup, Bjarne (1999). Többszörös öröklés a C++ számára Az 1987. tavaszi Európai Unix-felhasználók csoportjának konferenciájának anyaga
- Object-Oriented Software Construction, második kiadás, Bertrand Meyer, Prentice Hall, 1997, ISBN 0-13-629155-4