CodeGym/Java tanfolyam//Függőség inverziója

Függőség inverziója

Szint, Lecke
Elérhető

9.1 Függőség-inverzió

Emlékszel, egyszer azt mondtuk, hogy egy szerveralkalmazásban nem lehet csak úgy streameket létrehozni a következőn keresztül new Thread().start()? Csak a tárolónak kell szálakat létrehoznia. Ezt az ötletet most még tovább fejlesztjük.

Minden objektumot csak a tárolónak kell létrehoznia . Természetesen nem minden objektumról beszélünk, hanem inkább az úgynevezett üzleti objektumokról. Gyakran kukáknak is nevezik őket. Ennek a megközelítésnek a lábai a SOLID ötödik elvéből nőnek ki, amely megköveteli az osztályoktól való megszabadulást és az interfészekre való átállást:

  • A felső szintű modulok nem függhetnek az alacsonyabb szintű moduloktól. Mind ezeknek, mind másoknak az absztrakciótól kell függniük.
  • Az absztrakciók nem függhetnek a részletektől. A megvalósításnak az absztrakciótól kell függnie.

A modulok nem tartalmazhatnak hivatkozásokat konkrét implementációkra, és a köztük lévő összes függőséget és interakciót kizárólag absztrakciók (vagyis interfészek) alapján kell felépíteni. Ennek a szabálynak a lényege egy mondatban leírható: minden függőségnek interfész formájában kell lennie .

Alapvető jellege és látszólagos egyszerűsége ellenére ezt a szabályt leggyakrabban megsértik. Ugyanis minden alkalommal, amikor a program/modul kódjában az új operátort használjuk és egy adott típusú új objektumot hozunk létre, így az interfésztől való függés helyett az implementációtól való függés alakul ki.

Nyilvánvaló, hogy ezt nem lehet elkerülni, és valahol létre kell hozni a tárgyakat. De legalább minimálisra kell csökkentenie azoknak a helyeknek a számát, ahol ez megtörténik, és ahol az osztályok kifejezetten vannak megadva, valamint lokalizálni és el kell különíteni az ilyen helyeket, hogy ne legyenek szétszórva a programkódban.

Nagyon jó megoldás az az őrült ötlet, hogy az új objektumok létrehozását speciális objektumok és modulok - gyárak, szolgáltatási lokátorok, IoC konténerek - belül koncentrálják.

Bizonyos értelemben egy ilyen döntés követi az egyválasztási elvet, amely kimondja: "Ha egy szoftverrendszernek sok alternatívát kell támogatnia, ezek teljes listáját csak a rendszer egy modulja ismerheti . "

Ezért, ha a jövőben szükség lesz új opciók hozzáadására (vagy új megvalósításokra, mint az új objektumok létrehozása esetén), akkor elegendő csak azt a modult frissíteni, amely ezt az információt tartalmazza, és az összes többi modult. ez nem érinti, és folytatni tudják munkájukat.

1. példa

new ArrayList Ahelyett, hogy ilyesmit írna , érdemes lenne List.new()a JDK-nak megadnia egy levél megfelelő megvalósítását: ArrayList, LinkedList vagy akár ConcurrentList.

Például a fordító látja, hogy különböző szálakból hívják az objektumot, és egy szálbiztos megvalósítást tesz oda. Vagy túl sok beszúrás a lap közepén, akkor a megvalósítás a LinkedList alapján fog történni.

2. példa

Ez például a fajtákkal már megtörtént. Mikor írt utoljára rendezési algoritmust egy gyűjtemény rendezésére? Ehelyett most mindenki a metódust használja Collections.sort(), és a gyűjtemény elemeinek támogatniuk kell a Comparable felületet (összehasonlítható).

Ha sort()egy 10-nél kevesebb elemből álló gyűjteményt ad át a metódusnak, akkor teljesen lehetséges, hogy buborékos rendezéssel (Bubble sort) rendezze, és nem Quicksorttal.

3. példa

A fordító már figyeli, hogyan fűzi össze a karakterláncokat, és lecseréli a kódot a -ra StringBuilder.append().

9.2 Függőség-inverzió a gyakorlatban

Most a legérdekesebb: gondoljuk át, hogyan tudjuk összekapcsolni az elméletet és a gyakorlatot. Hogyan tudják a modulok helyesen létrehozni és fogadni „függőségeiket”, és nem sértik meg a függőségi inverziót?

Ehhez a modul tervezésekor el kell döntenie:

  • mit csinál a modul, milyen funkciót lát el;
  • akkor a modulnak szüksége van a környezetéből, vagyis milyen objektumokkal/modulokkal kell majd foglalkoznia;
  • És hogyan fogja megszerezni?

A Dependency Inversion elveinek való megfeleléshez feltétlenül el kell döntenie, hogy a modul mely külső objektumokat használja, és hogyan kap rájuk hivatkozásokat.

És itt vannak a következő lehetőségek:

  • a modul maga hoz létre objektumokat;
  • a modul objektumokat vesz a tárolóból;
  • a modulnak fogalma sincs, honnan származnak az objektumok.

A probléma az, hogy egy objektum létrehozásához egy adott típusú konstruktort kell meghívni, és ennek eredményeként a modul nem az interfésztől, hanem az adott megvalósítástól fog függni. De ha nem akarjuk, hogy az objektumok kifejezetten a modulkódban legyenek létrehozva, akkor használhatjuk a Factory Method mintát .

"A lényeg az, hogy ahelyett, hogy egy objektumot közvetlenül példányosítanánk újon keresztül, a kliens osztály számára biztosítunk valamilyen felületet az objektumok létrehozásához. Mivel egy ilyen interfész a megfelelő kialakítással mindig felülbírálható, némi rugalmasságot kapunk az alacsony szintű modulok használatakor. magas szintű modulokban" .

Azokban az esetekben, amikor összefüggő objektumcsoportokat vagy családokat kell létrehozni, a Factory Method helyett egy Absztrakt gyárat kell használni .

9.3 A szervizkereső használata

A modul átveszi a szükséges objektumokat attól, aki már rendelkezik velük. Feltételezzük, hogy a rendszer rendelkezik valamilyen objektumtárral, amelybe a modulok „behelyezhetik” az objektumaikat, és „elvehetik” az objektumokat a tárolóból.

Ezt a megközelítést a Service Locator minta valósítja meg , amelynek fő gondolata az, hogy a programnak van egy objektuma, amely tudja, hogyan szerezheti be az összes szükséges függőséget (szolgáltatást).

A fő különbség a gyáriaktól az, hogy a Service Locator nem hoz létre objektumokat, hanem valójában már tartalmaz példányos objektumokat (vagy tudja, hol / hogyan szerezheti be őket, és ha létrehozza, akkor csak egyszer az első hívásnál). A gyár minden híváskor létrehoz egy új objektumot, amelynek teljes tulajdonjogát Ön kapja meg, és bármit megtehet vele.

Fontos ! A szolgáltatáskereső ugyanazokra a már meglévő objektumokra hivatkozik . Ezért nagyon óvatosnak kell lennie a Szolgáltatáskereső által kiadott tárgyakkal, mert más is használhatja őket Önnel egy időben.

A Service Locator objektumai közvetlenül a konfigurációs fájlon keresztül adhatók hozzá, a programozó számára bármilyen módon. Maga a Service Locator lehet egy statikus osztály statikus metódusokkal, egy szingli vagy egy interfész, és egy konstruktoron vagy metóduson keresztül átadható a szükséges osztályoknak.

A szolgáltatáskeresőt néha anti-mintának nevezik, és nem ajánlott (mert implicit kapcsolatokat hoz létre, és csak a jó tervezés látszatát kelti). Mark Seamantől többet olvashat:

9.4 Dependency Injection

A modul egyáltalán nem törődik a függőségek "bányászatával". Csak azt határozza meg, hogy mire van szüksége a működéséhez, és az összes szükséges függőséget kívülről szállítja (bevezeti) valaki más.

Ezt hívják - Dependency Injection. Általában a szükséges függőségek konstruktor paraméterként (Constructor Injection) vagy osztálymetódusokon (Setter injekció) keresztül kerülnek átadásra.

Ez a megközelítés megfordítja a függőségek létrehozásának folyamatát - maga a modul helyett a függőségek létrehozását valaki kívülről irányítja. Az objektumok aktív kibocsátójából származó modul passzívvá válik - nem ő alkot, hanem mások alkotnak neki.

Ezt az irányváltást az irányítás megfordításának , vagy hollywoodi elvnek nevezik – "Ne hívj minket, mi felhívunk."

Ez a legrugalmasabb megoldás, amely a legnagyobb autonómiát biztosítja a moduloknak . Elmondhatjuk, hogy csak ez valósítja meg maradéktalanul az „Egyedülálló felelősség elvét” – a modulnak teljes mértékben arra kell koncentrálnia, hogy jól végezze a munkáját, és ne törődjön semmi mással.

A modul ellátása a munkához szükséges mindennel külön feladat, amit a megfelelő „szakembernek” kell ellátnia (általában egy bizonyos konténer, egy IoC konténer felelős a függőségek kezeléséért és azok megvalósításáért).

Tulajdonképpen itt minden olyan, mint az életben: egy jól szervezett társaságban programozók programoznak, az íróasztalokat, számítógépeket és mindent, ami a munkához kell, az irodavezető veszi meg és biztosítja. Illetve, ha a program metaforáját konstruktorként használjuk, akkor a modul ne gondoljon vezetékekre, valaki más vesz részt a konstruktor összeállításában, nem maguk az alkatrészek.

Nem túlzás azt állítani, hogy az interfészek használata a modulok közötti függőségek leírására (Dependency Inversion) + e függőségek helyes létrehozása és beillesztése (elsősorban Dependency Injection) kulcsfontosságú technikák a szétválasztáshoz .

Ezek szolgálnak az alapjául, amelyen a kód laza csatolása, rugalmassága, a változtatásokkal szembeni ellenállása, újrafelhasználása ellenáll, és amely nélkül minden más technikának nincs értelme. Ez a laza tengelykapcsoló és a jó építészet alapja.

A vezérlés megfordításának elvét (a Dependency Injection és a Service Locator funkcióval együtt) Martin Fowler részletesen tárgyalja. Mindkét cikkének van fordítása: "A vezérlőtárolók megfordítása és a függőségi befecskendezési minta" és a "vezérlés megfordítása" .

Hozzászólások
  • Népszerű
  • Új
  • Régi
Hozzászólás írásához be kell jelentkeznie
Ennek az oldalnak még nincsenek megjegyzései