Objektumorientált programozásnál az öröklődés (angolul inheritance) mechanizmusa, hogy egy objektumot vagy egy osztályt alapjául választunk egy másik objektumnak (prototípus-alapú öröklődés) vagy osztálynak (osztályalapú öröklődés), megtartva a hasonló implementációt. Más néven egy új osztályt (alosztályt) származtatunk egy már létező szuper- vagy alaposztályból, amiket aztán osztályok hierarchiájává formázunk. A legtöbb osztály-alapú objektumorientált nyelvben egy öröklődésen keresztül létrehozott objektum a „gyermekobjektum” megkapja az ő „szülőobjektumának” minden tulajdonságát és viselkedését, leszámítva a konstruktorokat, destruktorokat, túlterhelt operátorait és barát függvényeit. Az öröklődés lehetővé teszi, hogy már létező osztályokra hozzunk létre újakat, ezzel annak egy új implementációját megalkotva úgy, hogy közben a viselkedését megtartja (interfész implementálása). Ez hasznos, mivel újrahasznosításra kerül a kód, emellett publikus osztályok és interfészek segítségével szabadon bővíthető marad az eredeti szoftver. Objektumok vagy osztályok közötti öröklődés által kialakult kapcsolatok jól szemléltethetők irányított gráfokkal.
Az öröklődést 1969-ben találták fel a Simula nyelvhez, de azóta már számos egyéb objektumorientált nyelv is átvette, például a Java, a C++ vagy a Python.
Egy öröklő osztály az alosztálya az ő szuperosztályának. Rokonsági metaforákkal szülőosztályról és gyermekosztályokról vagy ősosztályról és leszármazott osztályokról is beszélhetünk. Az „öröklődés” kifejezést ritkábban szoktuk használni mind az osztály-alapú, mint a prototípus-alapú programozás esetében, azonban szigorúbban véve a kifejezés az osztályalapú programozás szótárának része (egyik osztály örököl a másiktól). Ennek megfelelője a prototípus-alapú programozásban a delegáció (egyik osztály a másikhoz delegálódik).
Az öröklődés nem összetévesztendő az altípusokkal. Bizonyos nyelvek esetén az öröklődés és altípusok megegyeznek, mások esetén nem; általánosságban elmondható, hogy az altípusok is-a kapcsolatban állnak, míg az öröklődés csak újrahasznosítja az implementációt és inkább szintaktikai kapcsolatot hoz létre, nem szükségszerűen szemantikait (az öröklődés nem feltétlen vonja magával a viselkedési altípusokat). Hogy különbséget tehessünk ezen fogalmak közt, az altípusokat interfészöröklődésnek is szoktuk nevezni, míg az öröklődés, a jelen írt definíció szerint is implementációöröklődés vagy kód öröklődés. Ugyanakkor az öröklődés egy gyakran használt módja altípus kapcsolatok létrehozásának.
Az öröklődést az objektum-összetétellel szokás szembe állítani, ahol egy objektum tartalmazza a másik objektumot (vagy egy osztályból származó objektum tartalmazza egy másik osztály objektumait); lásd: Öröklődés helyett objektum-összetétel. A kompozíció has-a kapcsolatot implementál, szemben az altípusok is-a kapcsolatával.
Típusok
Az öröklődésnek számos típusa létezik, melyeket minták és konkrét nyelvek határoznak meg.
Egyszeres öröklődés ahol az alosztályok megörökölik egy szuperosztály tulajdonságait. Egy osztály szert tesz egy másik osztály funkcióira. Többszörös öröklődés mikor egy osztály rendelkezhet egynél több szuperosztállyal és az összes szülőosztály tulajdonságait megörököli.
Többszintű öröklődés
mikor egy alosztály egy másik alosztály leszármazottja. Nem ritka, hogy egy osztály egy származtatott osztályból származik, ahogy ezt a „Többszintű öröklődés” ábra is bemutatja. Az A osztály alaposztályként szolgál a származtatott B osztálynak, mely továbbá alaposztályként szolgál az abból származtatott C osztálynak. A B osztályt szokás közbülső alaposztálynak is nevezni, mert egyfajta összeköttetést biztosít az A és C közötti öröklődésben. Az ABC láncot öröklődési láncnak hívják.
Többszintű öröklődés során osztályszármaztatásnak deklarációja:
Class A(...); // Alaposztály Class B : public A(...); // B származik az A-ból Class C : public B(...); // C származik a B-ből
Ezt a folyamatot a végtelenségig lehet végezni.
Hierarchikus öröklődés
Ebben az esetben egy osztály több alosztály számára szolgál alaposztályként. Például egy A szülőosztály rendelkezhet kettő alosztállyal, B-vel és C-vel. B-nek és C-nek is A az ősosztálya, de B és C két egymástól különálló alosztály.
Hibrid öröklődés
Erről akkor beszélhetünk, mikor kettő-, vagy többfajta öröklődés egyszerre áll fenn a fentiek közül. Például az A osztály rendelkezik egy B alosztállyal, melynek van két alosztálya, C és D. Ez a többszintű öröklődés és a hierarchikus öröklődés keveréke.
Alosztályok és szuperosztályok
Alosztályok, származtatott osztályok, örökösosztályok, vagy gyermekosztályok valójában mind moduláris származtatott osztályok melyek egy, vagy több nyelvi elemet örökölnek egy vagy több egyéb osztályokból (szuperosztály, alaposztály, szülő- vagy ősosztály). Az osztályöröklődés szemantikája nyelvenként eltérő lehet, de az alosztály általában automatikusan örököli a szuperosztály példányszintű változóit és tagfüggvényeit.
Származtatott osztály létrehozásának általános bemutatása:
class Alosztály: láthatóság Ősosztály { // alosztály tagok
A kettőspont reprezentálja, hogy az alosztály örököl a szuperosztálytól. A visibility (láthatóság) opcionális, private vagy public értékeket vehet fel. Az alapértelmezett láthatóság a private. A láthatóság határozza meg hogy az ősosztály metódusai és mezői publikusan-, vagy privát módon származódnak.
Néhány nyelv más szerkezetek öröklését is támogatják. Az Eiffel-ben például a contract-okat, melyek specifikációval látják el az osztályokat, az utódok szintén képesek örökölni. Az ősosztály létrehoz egy közös interfészt és egy alapvető függőséget, melyet speciális alosztályok képesek örökölni, módosítani és bővíteni. Egy alosztály által örökölt szoftver az alosztály szempontjából újrahasznosítottnak minősül. Egy osztály példányára mutató referencia lehet, hogy valójában annak egy alosztályára mutat. A mutatott objektum valódi osztályát lehetetlen meghatározni fordításidőben. Számos különböző osztály tagfüggvényeinek objektumai egy egységes interfészen keresztül kerülnek meghívásra. Az alosztályok akár teljesen felülírhatják az ősosztály függvényeit amennyiben megegyezik a metódus szignatúra.
Nem-alosztályosítható osztályok
Egyes nyelvek esetén az osztályok definiálhatók nem-alosztályosítható osztályként bizonyos osztálymódosító kulcsszavak segítségével. Ilyenek például a final kulcsszó Java és C++11 és újabb verziójában, vagy a sealed C#-ban. Ezen kulcsszavak az osztálydeklaráció során kerülnek feltüntetésre a class szó előtt. Az ilyen nem-alosztályosítható osztályok korlátozzák az újrahasznosíthatóságot, kiváltképp mikor a fejlesztők a forráskódhoz nem férnek hozzá, csak az előrefordított binárisához.
Egy nem-alosztályosítható osztálynak nincsenek alosztályai, ezért már fordítási időben is egyértelmű, hogy az ennek az osztálynak objektumaira mutató referenciák, vagy pointerek valóban ennek az osztálynak példányaira mutatnak, nem pedig alosztályainak példányaira (nincsenek) vagy szuperosztályainak példányaira (egy referenciatípus felfelé kasztolása sérti a típusrendszert). Mivel a mutatott objektum pontos típusa már a futtatás előtt ismert, használható korai kötés (static dispatch) késői kötés (dynamic dispatch) helyett, melyhez szükség van legalább egy virtuális metódustábla keresésre annak függvényében, hogy a használt programozási nyelv támogatja-e a többszörös örököltetést, vagy csak az egyszeres örököltetést.
Nem-felülírható metódusok
Ahogy egy osztály lehet nem-alosztályosítható, úgy a metódus deklarációk is tartalmazhatnak olyan kulcsszavakat, melyek megakadályozzák, hogy felülírásra kerüljön az (vagyis, hogy egy alosztályban kicseréljék azt egy új metódusra, mellyel a neve és szignatúrája megegyezik). Egy privát metódus nem írható felül egyszerűen azért, mert a saját osztályán kívül senki más nem fér hozzá (habár ez a C++-ra nem igaz). Java-ban egy final metódus, C#-ban egy sealed metódus vagy Eiffel-ben egy frozen metódus nem felülírható.
Virtuális metódusok
Hogyha a szuperosztály metódusa virtuális, a metódus-hozzárendelés dinamikusan történik. Egyes nyelvek megkívánják, hogy ezeket a metódusokat expliciten virtuálisként hozzuk létre (pl.: C++), míg másokban minden metódus alapból virtuális (pl.: Java). Egy nem-virtuális metódushíváshoz történő metódus-hozzárendelés mindig statikus módon kerül lekötésre (a függvényhívás címe fordításidőben dől el). A statikus átadás gyorsabb, mint a dinamikus.
Örökölt tagok láthatósága Source: Alaposztály láthatóságSzármazó osztály láthatóság Public származtatás Private származtatás Protected származtatásPrivate → Protected → Public →Nem örökölt Protected PublicNem örökölt Private PrivateNem örökölt Protected Protected
Alkalmazása Örököltetéssel képesek vagyunk összekapcsolni egy, vagy több osztályt egymással.
Felülírás
Számos objektumorientált programozási nyelv lehetővé teszi osztályainak vagy objektumainak, hogy kicseréljék egy általa örökölt aspektus implementációját (általában egy viselkedés). Ez a folyamat a felülírás. A felülírás egy problémát szül: a viselkedés melyik verzióját használja az örökölő osztály példánya – azt, amelyik az ő saját osztályának része, vagy amelyik az ő szülőosztályából származik? A válasz nyelvenként különbözik, emellett néhány osztály lehetőséget biztosít arra, hogy jelezzük az adott viselkedésnek, hogy azt nem lehet felülírni, úgy kell viselkednie, ahogy azt az alaposztály definiálta. Például C#-ban az alap metódus, vagy property csak akkor felülírható egy alosztályban, ha ellátták őket virtual, abstract vagy override kulcsszavak valamelyikével, míg például a Java esetében különböző metódusok hívhatók meg, hogy felülírjanak metódusokat.override(C# Reference) Felülírás alternatívája az örökölt kód elrejtése.
Kódújrahasznosítás Az implementációöröklődés egy olyan folyamat, ahol az alosztály az alaposztály kódját felhasználja. Alapértelmezés szerint az alosztály megtartja az ősének összes funkcióját, de az alosztály felülírhatja akár az összes metódusát kicserélve ezzel az alaposztály implementációját az ő sajátjára.
Az alábbi Python példában a és a alosztályok fölülírják a alaposztály metódusát. Az alaposztály tartalmaz műveleteket, mely kiszámolja két szám négyzetének összegét. Az alosztály újrahasznosítja az alaposztály összes funkcionalitását, kivéve azt, mely a szám négyzetét számolja ki. Helyette felülírja egy olyan metódussal, mely vagy a négyzetét vagy a köbét számolja ki az adott számnak. Ennek megfelelően az alosztályok a számok négyzetének/köbének összegét számolják ki.
Alább látható a példa Pythonban. class SumComputer(object): def __init__(self, a, b): self.a = a self.b = b
def transform(self, x): raise NotImplementedError
def inputs(self): return range(self.a, self.b)
def compute(self): return sum(self.transform(value) for value in self.inputs())
class SquareSumComputer(SumComputer): def transform(self, x): return x * x
class CubeSumComputer(SumComputer): def transform(self, x): return x * x * x
A világ nagyrészén pusztán a kódújrahasznosításért használt osztályöröklés már divatjátmúlt. Ennek legfőbb oka az, hogy az implementációöröklés nem biztosítja a polimorfikus helyettesíthetőséget – egy újrahasznosító osztály példánya nem feltétlen helyettesíthető az örökölt osztály példányával. Egy alternatíva erre az explicit delegáció, több programozási erőbefektetésre van szükség, de elhárítja a kicserélhetőség problémáját. C++ -ban a private öröklődés használható az implementációöröklődés egy formájaként kicserélhetőség nélkül. Miközben a public öröklődés egy "is-a" kapcsolatot reprezentál, a delegáció pedig "has-a"-t, a private (és protected) öröklődésre gondolhatunk "A implementálva B szerint" kapcsolatként.