A függőség megfordításának elve

(Függőség megfordítása szócikkből átirányítva)

Az objektumorientált programtervezésben a függőség megfordításának elve a programmodulok függőségeinek speciális formája. Az elv követésével nem a magasabb szintű modulok függenek az alacsonyabb szintűektől, hanem fordítva, az alacsonyabb szintűek a magasabb szintűektől. Az elv a következőket követeli meg:[1]

  • A magas szintű kód nem függ az alacsonyabb szintűtől. Mindkettő absztrakciótól függ.
  • Az absztrakciók nem függenek a részletektől. A részletek absztrakciótól függenek.

Mivel az alacsony és a magas szintű kód ugyanattól az absztrakciótól függ, megfordítja a gondolkodást az objektumorientációtól.[2]

Az elképzelésnek az az alapötlete, hogy amikor megtervezzük a magas és az alacsony szintű kód kapcsolatát, a köztük levő interakciót elvonatkoztatjuk, absztrakttá tesszük. Az alacsony szintű kódról is másként kell gondolkodni: gondolni kell az interakcióra, és eszerint megváltoztatni az interfészét.

Sok esetben már az is a duplikátumok csökkenését eredményezi, hogy absztrakcióként gondolunk az interakcióra. Ezzel könnyebb és kevésbé implementációfüggő interakciós sémát kapunk.

Ha az interakciót érdemes generikussá tenni, akkor a kontroll megfordításához jutunk.

Hagyományos réteges minta szerkesztés

A hagyományos alkalmazásszerkezetben az alacsony szintű komponenseket arra tervezik, hogy magasabb szintű komponensek használják őket. A magas szintű komponensek közvetlenül függenek az alacsonyabb szintűektől. Ez a függés korlátozza a magasabb szintű komponensek újrafelhasználását.[1]

 

A függőség megfordításának az a célja, hogy meglazítsa ezt a szoros csatolást egy absztrakt réteg közbeiktatásával, amivel javítja a magasabb szintű kód újrafelhasználhatóságát és tesztelhetőségét.

A függőség megfordításának szerkezete szerkesztés

Az absztrakt réteg közbeiktatásával gyengül a felsőbb szint függése az alacsonyabb szintűtől. A megfordítás szó nem arra utal, hogy most az alacsony szint függ a magas szinttől. Mindkét réteg az absztrakciótól függ.

 

Közvetlen alkalmazás esetén az absztrakciókat a felsőbb rétegek birtokolják. Ebben a szerkezetben a felsőbb rétegek és az absztrakciók egy csomagban vannak. Az alacsonyabb szintű rétegek az absztrakciókból örökölnek, vagy azokat valósítják meg.[1]

A függőség megfordítása és a tulajdonosi szerkezet támogatja az újrafelhasználhatóságot. A felsőbb rétegek így más megvalósításokat is tudnak használni. Ekkor néha adapterre (lásd Illesztő programtervezési minta) is szükség van.

Általánosítások szerkesztés

Sok projektben a függőség megfordítását általánosítják. Erre legalább két indok van:

  • A minta megtestesíti azt a gondolkodási elvet, hogy mindenről fogalmat alkotunk, ennek megfelelői az absztrakciók. Ez egy jó gondolkodási elv. Ha elkészül egy interfész vagy absztrakt osztály, akkor egy absztrakció készült el.
  • Az egységtesztelő eszközök az öröklésre hagyatkoznak, hogy mókolják a valós objektumokat. Generikus interfészekkel ez könnyű.

Ha az eszköz csak öröklésre és megvalósításra hagyatkozik, akkor általánosítani kell a függőség megfordítását. Ennek több hátránya van.

  • Nem elégséges egy osztály interfésztét implementálni, és általában ezzel nem csökken a függés. Az csak a potenciális interakciókról való gondolkodással érhető el.
  • A generikus interfészek általános jelenléte megnehezíti a program megértését és fenntartását. Az olvasó mindenütt ellenőrzi az összes alternatív implementációt, és mókoló objektumokon kívül csak ritkán talál mást.
  • Az általánosítás nehézkesebbé teszi a kódot. Gyárakat kell használni, amelyekhez általában dependency injection keretrendszereket hoznak be.
  • Az interfészek általánosítása a programozási nyelv használatát is korlátozza.

Az általánosítás korlátai szerkesztés

Az interfészek jelenlétének további következményei is vannak:

  • A tagváltozók csak interfészek, absztrakt osztályúak vagy alapelemek (karakterek, számok, logikai értékek) lehetnek.
  • Minden konkrét osztály csak interfészeken vagy absztrakt osztályokon keresztül kapcsolódhat.
  • Konkrét osztályból nem lehet örökölni.
  • Metódus megvalósítása nem írható felül.
  • Minden példányosítás létrehozási mintát (gyárat, gyártó metódust,…) igényel, vagy dependency injectiont.

Interfész mókolásának korlátai szerkesztés

Az interfész alapú mókolás miatt a következő korlátozásokra kell figyelni:

  • A kívülről is látható osztálytagoknak dependecy injectionre kell hagyatkozniuk, ami nehezebbé teszi megvalósításukat.
  • Minden tesztelhető metódusnak interfész megvalósításnak vagy absztrakt definíció felülírásának kell lennie.

Későbbi irányok szerkesztés

Az elvek gondolkodásmódok, a tervminták problémák megoldására szolgálnak. A minták a programnyelvekből hiányzó eszközöket is megvalósítanak.

  • A szerződések pontosabb használata legalább két irányban: feltételek kikényszerítése és állapot alapú interfészek. Ez a legtöbb helyzetben egyszerűsíti és bátorítja a függőség megfordításának használatát.
  • Egyre több mókoló eszköz kód injekciót használ az osztályváltozók, osztályfüggvények, és a nem virtuális tagok számára. A programozási nyelvek számára valószínűleg mókoláskompatibilis

bájtkódot alakítanak ki. Az egyik korlátozza a nem virtuális tagok használatát, a másik lehetővé teszi nem öröklésen alapuló mock objektumok bájtkódjának generálását, legalábbis teszt üzemmódban.

Megvalósításai szerkesztés

A megvalósításoknak két különböző szerkezete lehetséges.

Az egyik módszer szerint a magasabb szintű osztályok és a szolgáltatások absztrakciói ugyanabba a csomagba kerülnek. Ebben az elrendezésben az alacsony szintű osztályok egy másik csomagban találhatók, míg az interfészek a magasabb szintű osztályokkal együtt. Mivel az alacsonyabb szintű osztályok az interfészeket valósítják meg, azért az alacsonyabb szintű osztályokat tartalmazó csomag függ a magasabb szintűtől, ami a hagyományos függés megfordítása.

 

Az 1-es és a 2-es ábra ugyanazt a működést végző kódot mutatja be, de a 2-es ábrán interfészt használtak a függés megfordítására. A függés iránya megválasztható úgy, hogy támogassa a kód újrafelhasználását, és kiküszöbölje a ciklikus függőségeket.

Ebben a változatban a függőség megfordítása megnehezíti az alsóbb réteg újrafelhasználását. Ez csak a szokásos függést fordítja meg.

Ez kiküszöbölhető azzal, hogy az interfészek külön csomagban kapnak helyet:

 

A rétegeknek ez az elkülönítése lehetővé teszi mindegyik újrafelhasználását, ami növeli a robosztusságot és a mobilitást.[1]

Példák szerkesztés

Családfa modul szerkesztés

Egy családfa rokonsági kapcsolatokat jelenít meg (szülő-gyermek, férj-feleség, feleség-férj). Ez nagyon hatékony, és kibővíthető más kapcsolatokkal, mint elvált házastárs, vagy gyám. Mindez nagyon hatékony, de egy magasabb szint egyszerűbb navigációt igényel, hogy egy személy rokonait elérhesse.

Felhasználástól függően gráf helyett a közvetlen rokonságot különböző tulajdonságokként való tárolás könnyebbé teszi a kapcsolatot, és megengedi a belső reprezentáció lecserélését anélkül, hogy a használó modulok bármit is észrevennének. Lehetővé teszi a pontos definíciókat, ami megengedi az egyetlen felelősség elvének érvényesítését.

Végül, ha az első megközelítés látszik a legjobban kiterjeszthetőnek, a családfa modul felhasználása megmutathatja, hogy egy jobban specializált és leegyszerűsített megvalósítás elégséges az alkalmazáshoz, és hatékonyabb rendszer építését teszi lehetővé.

Ebben a példában a modulok közötti kapcsolat elvonatkoztatása az alacsonyabb szintű modul interfészének egyszerűsítését és ennek következtében a megvalósítás egyszerűsítését is eredményezheti.

Távoli fájlszerver kliens szerkesztés

A feladat egy távoli fájlszerver (FTP, felhő tárhely) kliensének megvalósítása. A következő absztrakt interfészek halmazaként lehet gondolni rá:

  • Kapcsolat felvétele és bontása
  • Könyvtárak létrehozása, törlése, átnevezése, kiíratása
  • Fájlok létrehozása, törlése, átnevezése, helyettesítése, olvasása
  • Fájlok keresése
  • Több fájl konkurrens kezelése
  • Fájltörténet kezelése

Ha a helyi és a távoli fájlok interfésze megegyezik, bármely magasabb szintű modul használható, ami a függőség megfordítását követi, és képes a helyi fájlokkal működni. A szerveren lehetnek másként reprezentálva a könyvtárak, mint helyben. Lehet, hogy a távoli fájlokat nem akarjuk közvetlenül írni vagy olvasni, mivel lehet, hogy a szerver túl lassú. Az is lehet, hogy az olvasás-írás csak szekvenciálisan végezhető. A keresés rábízható a helyi keresőkre. A műveletek konkurrens végzése a többi interfészt is érintheti.

A kliens tervezésekor minden felmerülő interfésznél meg kell kérdezni, hogy milyen szintű szolgáltatásokra van szüksége a magasabb szintnek, mivel nem biztos, hogy mindenre van igény. Azt a kérdést is fel kell tenni, hogy lehet a már létező szolgáltatásokkal kompatibilissé tenni az újonnan bevezetni kívántat.

Az interfészek megtervezése után a kliensnek meg kell valósítania őket. Amennyiben korlátozottabb funkcionalitásra van szükség, adaptereket kell használni. Emellett írni kell modulokat, amelyek megtalálják az összes helyi fájlrendszert. Miután ez megtörtént, az alkalmazás képes lesz kezelni a távoli fájlokat is, a helyiekhez hasonlóan. Sőt, a magasabb szintű modul képes lesz más rendszereken is felhasználni a fájlkezelő rendszert, ha az megfelel az általa elvárt interfésznek. Mindez újrafelhasználás.

Ebben a példában a modulra mint interfészek halmazára gondolva több fájlkezelő rendszer közös interfészét használták fel.

Modell-nézet-vezérlő szerkesztés

 
Modell-nézet-vezérlő a függőség megfordításának elvét használva

A példában az UI és az ApplicationLayer nagyrészt konkrét osztályokat tartalmaz. A vezérlók absztrakt/interfész típusokat tartalmaznak. Az UI az ICustomerHandler példánya. A csomagok fizikailag elkülönülnek. Az ApplicationLayer csomagnak része egy konkrét megvalósítás, amit a Page osztály használ. Ennek az interfésznek a példányait dinamikusan hozza létre egy Factory, ami benne lehet a Controllers csomagban. A Page és a CustomerHandler konkrét osztályok nem függenek egymástól; mindkettő az ICustomerHandlertől függ.

Ennek közvetlen eredménye az, hogy az UI nem hivatkozik közvetlenül az ApplicationLayerre vagy bármely más csomagra, ami az ICustomerHandler interfészt valósítja meg. A konkrét osztály reflexió használatával töltődik be. A konkrét osztály bármely pillanatban kicserélhető egy másik konkrét osztályra, ami ugyanezt az interfészt valósítja meg. Egy másik lehetőség, ha a Page osztály megvalósít egy IPageViewer interfészt, ami argumentumként átadható ICustomerHandler metódusoknak. Ekkor a konkrét megvalósítás konkrét függés nélkül kommunikálhat az UI-vel.

Kapcsolódó minták szerkesztés

A modell-nézet-vezérlő elvének alkalmazása az illesztő programtervezési minta példájának tekinthető. A magasabb szintű kód definiálja az adapter interfészét, ami egy absztrakció. Az adaptált megvalósítása szintén függ az adapter interfészétől, hiszen azt valósítja meg, míg saját maga megvalósítható azzal a kóddal, ami az ő moduljában van. A magasabb szintű nem függ tőle, hiszen csak azokat a metódusokat hívja, amelyeket az interfész követel meg.

Különböző minták, mint a plugin, szolgáltatáslokátor, vagy a kontroll megfordítása támogatják az igényelt komponens kiközvetítését a magasabb szintűnek.

Története szerkesztés

A függőség megfordítását Robert C. Martin fogalmazta meg, és több cikkében is írt róla. Cikkei a következőkben jelentek meg: Object Oriented Design Quality Metrics: an analysis of dependencies,[3] C++ Report 1996 májusi számában The Dependency Inversion Principle címmel;[4] továbbá könyvekben, mint Agile Software Development, Principles, Patterns, and Practices, és Agile Principles, Patterns, and Practices in C#.

Jegyzetek szerkesztés

  1. a b c d Agile Software Development, Principles, Patterns, and Practices. Prentice Hall, 127–131. o. (2003). ISBN 978-0135974445 
  2. (2004) „Head First Design Patterns” (paperback) 1, Kiadó: O'REILLY. (Hozzáférés: 2012. június 21.)  
  3. Martin, Robert C.: Object Oriented Design Quality Metrics: An analysis of dependencies. (Hozzáférés: 2016. október 15.)
  4. Martin, Robert C.: The Dependency Inversion Principle. C++ Report, 1996. május 1. [2011. július 14-i dátummal az eredetiből archiválva].

Források szerkesztés

Fordítás szerkesztés

  • Ez a szócikk részben vagy egészben a Dependency inversion principle című angol Wikipédia-szócikk 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.