Ezt a szócikket össze kellene dolgozni a GoF 2 alapelv szócikkel. Az összedolgozás után az egyik lapot törölni kell, vagy – ha érdemes – átirányítássá alakítani. A megbeszélésbe a vitalapon kapcsolódhatsz be.
Az objektumorientált programozásban (OOP) öröklődés helyett objektum-összetétel (más néven összetétel újrafelhasználási elv) alapja, hogy az osztályoknak polimorf viselkedést és kód-újrafelhasználást kell megvalósítania a kompozíciójukkal (azáltal, hogy saját példányokat tárolnak más osztályokból, melyek implementálják a kívánt funkcionalitást) ahelyett, hogy egy ősosztályból öröklődnének. Más néven GOF2 néven ismert.
Alapok
Az elv implementációja tipikusan azzal kezdődik, hogy különféle interfészeket készítünk, melyek leírják a viselkedéseket, melyeket a rendszernek tartalmaznia kell. Az interfészek teszik lehetővé a polimorfizmust. Az osztályok implementálják az interfészeket, így öröklődés nélkül megadhatjuk a rendszer egyes részeinek a működési viselkedését.
Az osztályok lehetnek ősosztályok bármiféle öröklődés nélkül. Egy alternatív implementációja a rendszer viselkedéseinek megvalósítható úgy, hogy új osztályt írunk, amely implementálja a kívánt viselkedés interfészét. Egy osztály, amely tartalmaz referenciát egy interfészhez segítheti az interfész implementálását, egy döntés, amelyet a futási időig meghozhatunk.
#include<vector>classModel;classGameObject{public:virtual~GameObject()=default;virtualvoidUpdate(){}virtualvoidDraw(){}virtualvoidCollide(conststd::vector<GameObject*>&objects){}};classVisible:publicGameObject{public:voidDraw()override{// Draw |model_| at position of this object.};private:Model*model_;};classSolid:publicGameObject{public:voidCollide(conststd::vector<GameObject*>&objects)override{// Check and react to collisions with |objects|.};};classMovable:publicGameObject{public:voidUpdate()override{// Update position.};};
Vannak konkrét osztályaink:
Játékososztály - amely Test, Mozgatható és Látható
Felhő osztály - amely Mozgatható és Látható, de nem Test
Épület osztály - amely Test és Látható, de nem Mozgatható
Csapda osztály - amely Test, de se nem Látható, se nem Mozgatható
Figyelembe kell venni, hogy az öröklődéssel egyes esetekben súlyos problémákba ütközhetünk, ha nincs kellő gondossággal implementálva. Például a gyémántproblémához[2] vezethet. Egyik megoldás ennek elkerülésére, hogy ilyen osztályokat készítsünk: LáthatóÉsTest,LáthatóÉsMozgatható, LáthatóÉsTestÉsMozgatható, és mindezt az összes lehetséges kombinációra létrehozzuk. Ez viszont rengeteg kódismétléshez vezet. A C++ megoldja a gyémántproblémát azzal, hogy engedi a virtuális öröklődést.
Kompozíció és interfészek
A következő C # példa bemutatja a kompozíció és az interfészek használatának elvét, hogy megvalósuljon a kód újrafelhasználás, és a polimorfizmus.
Ezen tervezési minta előnye, hogy nagyobb fokú flexibilitást nyújt. Sokkal természetesebb felépíteni az osztályokat különböző komponensekből, mint közös tulajdonságokat keresve felépíteni azokat egymásból. Például a gázpedál és a kormány nagyon kevés közös tulajdonsággal rendelkezik, mégis mindkét komponens létfontosságú egy autóban. A kompozíció sokkal stabilabb osztályszerkezetet biztosít, átláthatóbb lesz tőle a rendszer felépítése. Tehát sokkal jobb megfogalmazni, hogy egy osztály mire képes (HAS-A), mint hogy maga az osztály micsoda (IS-A).[1]
A kezdeti tervezés egyszerűsíthető azzal, hogy megfogalmazzuk a rendszer objektumainak viselkedéseit különféle interfészekben, ahelyett, hogy hierarchikus viszonyt építenénk fel az osztályok között öröklődés használatával. Ezzel a megközelítéssel a később felmerülő követelménybeli változtatások sokkal egyszerűbben kivitelezhetőek, amiket egyébként az osztályok teljes átalakításával oldhatnánk meg. Ezek mellett megoldást kínál arra a problémára is, mikor viszonylag kis változtatások szükségesek egy öröklődésalapú osztályszerkezetben, mely több osztálygenerációt is tartalmaz.
Néhány nyelv, nevezetesen a Go, kizárólag az objektum-összetételt használja.[3]
Hátrányok
Egy gyakori hátránya ezen módszer alkalmazásának, hogy adott komponensek által nyújtott metódusokat implementálni kell az örököltetett osztályban, még akkor is ha ezek csak a metódusokat továbbítják (ez igaz a legtöbb programozási nyelvre, de nem az összesre, lásd a lent kifejtett bekezdésben). Ezzel szemben az öröklődéskor nem szükséges az összes ősosztály metódusát újraimplementálni az örököltetett osztályban. Ehelyett az örököltetett osztály csak azokat a metódusokat implementálja (felülírja), melyek működése nem egyezik meg az osztály metódusaival. Ez sokkal kevesebb munkával jár, ha az ősosztály rengeteg metódust tartalmaz az alapműködéssel, és csak néhányat kell átírni az örököltetett osztályban.
Például az alábbi C# kódban a
Dolgozó
ősosztály változói és a metódusai átöröklődnek az
ÓrabérDolgozó
és
FixFizetésDolgozó
alosztályba. Csak a
Fizetés()
metódust kell implementálni (egyénileg) az összes örököltetett alosztályban. A többi metódust az ősosztály implementálja, és megosztja az összes alosztállyal; nem szükséges ezeket újraimplementálni (felülírni) vagy megemlíteni az alosztály definíciójakor.
// Base classpublicabstractclassEmployee{// PropertiesprotectedstringName{get;set;}protectedintID{get;set;}protecteddecimalPayRate{get;set;}protectedintHoursWorked{get;}// Get pay for the current pay periodpublicabstractdecimalPay();}// Derived subclasspublicclassHourlyEmployee:Employee{// Get pay for the current pay periodpublicoverridedecimalPay(){// Time worked is in hoursreturnHoursWorked*PayRate;}}// Derived subclasspublicclassSalariedEmployee:Employee{// Get pay for the current pay periodpublicoverridedecimalPay(){// Pay rate is annual salary instead of hourly ratereturnHoursWorked*PayRate/2087;}}
A hátrányok elkerülése
Egyes nyelvek speciális eszközöket kínálnak ezek mérséklésére:
Raku a handles kulcsszóval teszi lehetővé a metódustovábbítást.
A Java a Project Lombok[4] szolgáltatást nyújtja, amely lehetővé teszi a delegálás végrehajtását egyetlen @Delegate annotáció használatával a mezõn, ahelyett, hogy a delegált mezõbõl az összes módszer nevét és típusát másolná és megtartaná.[5] A Java 8 lehetővé teszi az interfészben alapértelmezett metódusok írását, hasonlóan a C#-hoz stb.
A Julia makrók felhasználhatók továbbítási módszerek előállítására. Számos megvalósítás létezik, mint például a Lazy.jl[6] és a TypedDelegation.jl.[7]
A Swift kiterjesztések felhasználhatók egy protokoll alapértelmezett megvalósításának meghatározására magán a protokollon, nem pedig az egyedi típus megvalósításán belül.[8]
Kotlin tartalmazza a delegálási mintát a nyelvi szintaxisban.[9]
A Go típus beágyazódás elkerüli a továbbítási módszerek szükségességét.[10]
D egy "alias this" deklarációt nyújt egy típuson belül, továbbítva benne minden metódust és egy másik tartalmazott típus tagját.[11]
A C # alapértelmezett metódusokat biztosít a 8.0 verzió óta az interfészekben, amely lehetővé teszi a interfész metódusok törzsének meghatározását.[12]
Gyakorlati tanulmányok
A 93 (különböző méretű) nyílt forráskódú Java programról szóló 2013. évi tanulmány megállapította, hogy:
„Miközben nincs nagy lehetősége az öröklődést lecserélni a kompozícióval, mégis jelentős lenne.”
(Az öröklődés használatának csupán 2%-a belső és további 22% kizárólag külső vagy belső újrafelhasználásra van használva.)
Az eredményeink azt sugallják, nincs szükség az öröklődés támadására (legalábbis a nyílt forráskódú Java-szoftverekben), de mégis felmerül a kompozíció vs. öröklődés kérdése. Ha jelentős költség az öröklődés számlájára írható fel, ott ahol kompozíciót lehetne használni, az aggodalomra ad okot.[13]
Ez a szócikk részben vagy egészben a Composition over 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.