Princip inverzije zavisnosti. Inverzija zavisnosti Slojevita arhitektura sa inverzijom zavisnosti

Pretplatite se
Pridružite se zajednici nloeda.ru!
U kontaktu sa:

Posljednje ažuriranje: 03.11.2016

Princip inverzije zavisnosti Princip inverzije zavisnosti se koristi za kreiranje labavo povezanih entiteta koje je lako testirati, modifikovati i ažurirati. Ovaj princip se može formulirati na sljedeći način:

Moduli najvišeg nivoa ne bi trebali ovisiti o modulima nižeg nivoa. I jedno i drugo mora zavisiti od apstrakcija.

Apstrakcije ne bi trebale zavisiti od detalja. Detalji moraju zavisiti od apstrakcija.

Da biste razumjeli princip, razmotrite sljedeći primjer:

Class Book (javni string Text ( get; set; ) public ConsolePrinter Printer ( get; set; ) public void Print() ( Printer.Print(Text); ) ) class ConsolePrinter (javni void Print(string text) ( Console.WriteLine (tekst);

Klasa Book, koja predstavlja knjigu, koristi klasu ConsolePrinter za štampanje. Sa ovom definicijom, klasa Book zavisi od klase ConsolePrinter. Štaviše, striktno smo definisali da se štampanje knjige može obaviti samo na konzoli koristeći klasu ConsolePrinter. Druge opcije, na primjer, izlaz na pisač, izlaz u datoteku ili korištenje nekih elemenata grafičkog sučelja - sve je to isključeno u ovom slučaju. Apstrakcija štampanja knjiga nije odvojena od detalja klase ConsolePrinter. Sve ovo predstavlja kršenje principa inverzije zavisnosti.

Sada pokušajmo da uskladimo naše klase sa principom inverzije zavisnosti, odvajajući apstrakcije od implementacije niskog nivoa:

Interfejs IPrinter ( void Print(string text); ) class Book (javni string Text ( get; set; ) javni IPrinter Printer ( get; set; ) public Book(IPrinter printer) ( this.Printer = printer; ) public void Print( ) ( Printer.Print(Text); ) ) klasa ConsolePrinter: IPrinter (javni void Print(string text) (Console.WriteLine("Print to console"); ) ) klasa HtmlPrinter: IPrinter (javni void Print(string text) ( Console.WriteLine("Štampaj u html-u");

Apstrakcija štampanja knjiga sada je odvojena od konkretnih implementacija. Kao rezultat toga, i klasa Book i klasa ConsolePrinter zavise od apstrakcije IPprintera. Osim toga, sada također možemo kreirati dodatne implementacije niskog nivoa apstrakcije IPintera i dinamički ih primijeniti u programu:

Knjiga knjiga = nova knjiga(novi ConsolePrinter()); book.Print(); book.Printer = novi HtmlPrinter(); book.Print();

, pluralitet interfejsi i inverzija zavisnosti. Pet agilnih principa koji bi vas trebali voditi svaki put kada pišete kod.

Bilo bi nepravedno reći vam da je bilo koji od SOLID principa važniji od drugog. Međutim, možda nijedan drugi princip nema tako neposredan i dubok uticaj na vaš kod kao princip inverzije zavisnosti ili skraćeno DIP. Ako smatrate da je druge principe teško razumjeti i primijeniti, trebali biste početi s ovim, a zatim primijeniti ostatak na kod koji već slijedi princip inverzije zavisnosti.

Definicija

O. Moduli višeg nivoa ne bi trebali zavisiti od modula nižeg nivoa. Svi oni moraju zavisiti od apstrakcija.
B. Apstrakcije ne treba da zavise od detalja. Detalji moraju zavisiti od apstrakcija.

Trebali bismo nastojati organizirati naš kod na osnovu ovih brojeva, a evo nekoliko tehnika koje mogu pomoći u tome. Maksimalna dužina funkcija ne bi trebala biti veća od četiri reda (pet uključujući i naslov), tako da se mogu u potpunosti uklopiti u naš um. Udubljenja ne bi trebalo da budu dublje od pet nivoa. Časovi sa najviše pet metoda. U obrascima dizajna, broj klasa koje se koriste obično se kreće od pet do devet. Naša arhitektura visokog nivoa iznad sadrži četiri do pet koncepata. Postoji pet SOLID principa, od kojih svaki zahtijeva pet do devet koncepata/modula/klasa za primjere. Idealna veličina razvojnog tima je između pet i devet. Idealan broj timova u kompaniji je takođe između pet i devet.

Kao što vidite, magični broj sedam, plus minus dva, je svuda, pa zašto bi vaš kod bio drugačiji?

ODRICANJE ODGOVORNOSTI: Autor ovog članka nema za cilj da potkopa autoritet ili na bilo koji način uvrijedi tako uvaženog suborca ​​kao što je "ujak" Bob Martin. Više se radi o pažljivijem razmišljanju o principu inverzije zavisnosti i analizi primjera koji se koriste za njegovo opisivanje.

U cijelom članku dat ću sve potrebne citate i primjere iz gore navedenih izvora. Ali da biste izbjegli “spoilere” i da vaše mišljenje ostane objektivno, preporučio bih da izdvojite 10-15 minuta i pročitate originalni opis ovog principa u članku ili knjizi.

Princip inverzije zavisnosti ide ovako:

O. Moduli najvišeg nivoa ne bi trebali ovisiti o modulima nižeg nivoa. I jedno i drugo mora zavisiti od apstrakcija.
B. Apstrakcije ne treba da zavise od detalja. Detalji moraju zavisiti od apstrakcija.
Počnimo s prvom tačkom.

Raslojavanje

Luk ima slojeve, kolači imaju slojeve, kanibali imaju slojeve, a softverski sistemi takođe imaju slojeve! – Shrek (c)
Svaki složeni sistem je hijerarhijski: svaki sloj je izgrađen na osnovu dokazanog sloja nižeg nivoa koji dobro funkcioniše. Ovo vam omogućava da se istovremeno fokusirate na ograničen skup koncepata, bez razmišljanja o tome kako se implementiraju osnovni slojevi.
Kao rezultat, dobijamo nešto poput sljedećeg dijagrama:

Slika 1 – „Naivna“ šema slojeva

Sa stanovišta Boba Martina, takva šema za podjelu sistema na slojeve jeste naivno. Nedostatak ovog dizajna je „podmukla karakteristika: sloj Policy zavisi od promena u svim slojevima na putu do Utility. Ova zavisnost je tranzitivna.» .

Hmm... Vrlo neobična izjava. Ako govorimo o .NET platformi, onda će zavisnost biti tranzitivna samo ako trenutni modul „izloži” module nižih nivoa u svom javnom interfejsu. Drugim riječima, ako je u MehanizamLayer postoji javna klasa koja uzima instancu kao argument StringUtil(od UtilityLayer), zatim svi klijenti na nivou MehanizamLayer postati zavisnik UtilityLayer. Inače, nema tranzitivnosti promjena: sve promjene nižeg nivoa su ograničene na trenutni nivo i ne šire se iznad.

Da biste razumjeli ideju Boba Martina, morate zapamtiti da je princip inverzije zavisnosti prvi put opisan još 1996. godine, a jezik C++ je korišten kao primjer. U originalnom članku to piše sam autor problem tranzitivnosti postoji samo u jezicima bez jasnog odvajanja interfejsa klase od implementacije. U C++, problem tranzitivnih zavisnosti je zaista relevantan: ako je datoteka PolicyLayer. h uključuje preko "include" direktive Sloj mehanizma. h, što zauzvrat uključuje UtilityLayer. h, zatim sa bilo kojom promjenom u datoteci zaglavlja UtilityLayer. h(čak i u “zatvorenom” dijelu klasa deklariranih u ovoj datoteci) morat ćemo ponovo kompajlirati i ponovo rasporediti sve klijente. Međutim, u C++ ovaj problem je riješen korištenjem PIml idioma, koji je predložio Herb Sutter i koji sada također nije toliko relevantan.

Rješenje ovog problema sa stanovišta Boba Martina je sljedeće:

“Sloj višeg nivoa deklarira apstraktno sučelje za usluge koje su mu potrebne. Donji slojevi se zatim implementiraju da zadovolje ova sučelja. Svaka klasa koja se nalazi na najvišem nivou pristupa sloju sloja pored nje preko apstraktnog interfejsa. Dakle, gornji slojevi su nezavisni od donjih. Naprotiv, niži slojevi zavise od interfejsa apstraktnih usluga, najavio na višem nivou... Tako smo preokretom ovisnosti stvorili strukturu koja je u isto vrijeme fleksibilnija, izdržljivija i mobilnija



Slika 2 – Obrnuti slojevi

Na neki način, ova podjela ima smisla. Tako, na primjer, kada se koristi obrazac promatrača, promatrani objekt (promatrani) definira sučelje za interakciju sa vanjskim svijetom, tako da nikakve vanjske promjene ne mogu utjecati na njega.

Ali s druge strane, kada je riječ o slojevima, koji su obično predstavljeni kao sklopovi (ili paketi u UML terminima), predloženi pristup se teško može nazvati održivim. Po definiciji, pomoćne klase nižeg nivoa koriste se u desetak različitih modula višeg nivoa. Uslužni slojće se koristiti ne samo u Sloj mehanizma, ali i u Sloj pristupa podacima, Transportni sloj, Neki drugi sloj. Da li onda treba implementirati interfejse definisane u svim modulima višeg nivoa?

Očigledno, ovo rješenje teško da je idealno, pogotovo jer rješavamo problem koji ne postoji na mnogim platformama, kao što su .NET ili Java.

Koncept apstrakcije

Mnogi pojmovi postaju toliko ukorijenjeni u našem mozgu da prestajemo obraćati pažnju na njih. Za većinu "objektno orijentisanih" programera, to znači da prestajemo da razmišljamo o mnogim preterano korišćenim terminima kao što su "apstrakcija", "polimorfizam", "enkapsulacija". Zašto razmišljati o njima, pošto je već sve jasno? ;)

Međutim, da bismo tačno razumeli značenje principa inverzije zavisnosti i drugog dela definicije, moramo se vratiti jednom od ovih fundamentalnih koncepata. Pogledajmo definiciju pojma "apstrakcija" iz knjige Gradi Bucha:

Apstrakcija identifikuje bitne karakteristike nekog objekta koje ga razlikuju od svih drugih vrsta objekata i na taj način jasno definiše njegove konceptualne granice sa stanovišta posmatrača.

Drugim riječima, apstrakcija definira vidljivo ponašanje objekta, koje je u terminima programskih jezika određeno javnim (i zaštićenim) sučeljem objekta. Vrlo često modeliramo apstrakcije koristeći interfejse ili apstraktne klase, iako sa OOP tačke gledišta to nije neophodno.

Vratimo se definiciji: Apstrakcije ne bi trebale zavisiti od detalja. Detalji moraju zavisiti od apstrakcija.

Koji primjer nam sada pada na pamet, nakon što se sjetimo o čemu se radi? apstrakcija? Kada apstrakcija počinje da zavisi od detalja? Primjer kršenja ovog principa je apstraktna klasa GZipStream, koji traje MemoryStream, a ne apstraktna klasa Potok:

Apstraktna klasa GZipStream ( // Apstrakcija GZipStream prihvaća GZipStream (MemoryStream memoryStream) () )

Drugi primjer kršenja ovog principa bi bila apstraktna klasa spremišta iz sloja pristupa podacima koji uzima u konstruktor PostgreSqlConnection ili niz povezivanja za SQL Server, koji svaku implementaciju takve apstrakcije čini vezanom za određenu implementaciju. Ali da li to misli Bob Martin? Sudeći prema primjerima navedenim u članku ili knjizi, Bob Martin pod pojmom „apstrakcije“ razumije nešto sasvim drugo.

PrincipDIPprema Martinu

Kako bi objasnio svoju definiciju, Bob Martin daje sljedeće objašnjenje.

Malo pojednostavljeno, ali i dalje vrlo efikasno tumačenje principa DIP-a izraženo je jednostavnim heurističkim pravilom: “Morate ovisiti o apstrakcijama”. U njemu se navodi da ne bi trebalo biti zavisnosti od specifičnih klasa; sve veze u programu moraju voditi do apstraktne klase ili interfejsa.

  • Ne bi trebalo postojati varijable koje pohranjuju reference na određene klase.
  • Ne bi trebalo da postoje klase koje proizilaze iz konkretnih klasa.
  • Ne bi trebalo postojati metode koje nadjačavaju metodu implementiranu u jednoj od osnovnih klasa.

Da bi se ilustrovalo kršenje principa DIP-a općenito, a posebno prva „pojašnjavajuća“ točka, dat je sljedeći primjer:

Dugme javne klase ( privatna lampa; javna void Poll() ( if (/* neki uslov */) lamp.TurnOn(); ) )

Sada se još jednom prisjetimo šta je to apstrakcija i odgovorite na pitanje: postoji li ovdje “apstrakcija” koja ovisi o detaljima? Dok razmišljate o ovome ili tražite pasus koji sadrži odgovor na ovo pitanje, želim da napravim malu digresiju.

Kod ima jednu zanimljivu osobinu. Uz rijetke izuzetke, sam kod ne može biti ispravan ili netačan; Da li je greška ili funkcija zavisi od toga šta se od nje očekuje. Čak i ako ne postoji formalna specifikacija (što je norma), kod je netačan samo ako radi nešto drugo od onoga što je potrebno ili predviđeno. To je princip koji je u osnovi ugovornog programiranja, u kojem su specifikacije (namjere) izražene direktno u kodu u obliku preduslova, postuvjeta i invarijanti.

Gledajući razred Dugme Ne mogu reći da li je dizajn pogrešan ili ne. Definitivno mogu reći da naziv klase ne odgovara njenoj implementaciji. Klasu treba preimenovati u LampButton ili ukloniti iz razreda Dugme polje Lamp.

Bob Martin insistira na tome da je ovaj dizajn pogrešan jer „strategija aplikacije na visokom nivou nije odvojena od implementacije niskog nivoa. Apstrakcije nisu odvojene od detalja. U nedostatku takvog razdvajanja, strategija najvišeg nivoa automatski zavisi od modula nižeg nivoa, a apstrakcija automatski zavisi od detalja."

prvo, Ne vidim "strategije najvišeg nivoa" i "module nižeg nivoa" u ovom primjeru: sa moje tačke gledišta, časovi Dugme I Lamp su na istom nivou apstrakcije (barem ne vidim argumente za suprotno). Činjenica da je klasa Dugme Mogućnost kontroliranja nekoga ne čini ga višim nivoom. Drugo, ovdje ne postoji „apstrakcija zavisna od detalja“, postoji „implementacija apstrakcije zavisna od detalja“, što uopće nije ista stvar.

Martinovo rješenje je:



Slika 3 – „Invertovanje zavisnosti“

Je li ovo rješenje bolje? Hajde da pogledamo…

Glavna prednost invertiranja zavisnosti „prema Martinu“ je inverzija vlasništva. U originalnom dizajnu, prilikom promjene klase Lamp razred bi morao da se promeni Dugme. Sada klasa Dugme"posjeduje" interfejs ButtonServer, ali se ne može promijeniti zbog promjena na „nižim nivoima“, kao npr Lamp. Upravo suprotno: promjena klase ButtonServer moguće samo pod uticajem promena u klasi Button, što će dovesti do promena u svim potomcima klase ButonServer!

14 odgovora

U osnovi kaže:

  • Apstrakcije nikada ne bi trebale zavisiti od detalja. Detalji moraju zavisiti od apstrakcija.

Što se tiče zašto je ovo važno, jednom riječju: promjena je rizična, a ovisno o konceptu, a ne implementaciji, smanjujete potrebu za promjenom na stranicama poziva.

Efektivno, DIP smanjuje spajanje između različitih dijelova koda. Ideja je da iako postoji mnogo načina za implementaciju, recimo, logera, način na koji ga koristite treba biti relativno stabilan tokom vremena. Ako možete izdvojiti sučelje koje predstavlja koncept evidentiranja, taj interfejs bi trebao biti mnogo stabilniji tokom vremena od njegove implementacije, a lokacije koje pozivaju trebale bi biti mnogo manje podložne promjenama koje možete napraviti održavanjem ili proširenjem tog mehanizma evidentiranja.

Budući da je implementacija specifična za sučelje, imate mogućnost da u vrijeme izvođenja odaberete koja implementacija je najprikladnija za vaše specifično okruženje. Ovisno o slučaju, i ovo može biti zanimljivo.

Knjige Agilni razvoj softvera, Principi, obrasci i prakse i Agilni principi, obrasci i prakse u C# su najbolji resursi za potpuno razumijevanje originalnih ciljeva i motiva koji stoje iza Principa inverzije zavisnosti. Članak "Princip preokreta ovisnosti" je također dobar izvor, ali zbog činjenice da je to sažeta verzija nacrta koja je na kraju završila u prethodno spomenutim knjigama, ostavlja iza sebe neke važne rasprave o konceptu vlasništva paketa. i interfejsi koji su ključni za Ovaj princip se razlikuje od opštijeg saveta za "program za interfejs, a ne za implementaciju" koji se nalazi u knjizi Design Patterns (Gamma, et al.).

Da rezimiramo, princip inverzije zavisnosti prvenstveno ima za cilj promijeniti tradicionalno kanalisanje zavisnosti sa komponenti "višeg nivoa" na komponente "nižeg nivoa", tako da komponente "nižeg nivoa" zavise od interfejsa, pripadanje na komponente "višeg nivoa" (Napomena: komponenta "višeg nivoa" ovdje se odnosi na komponentu koja zahtijeva vanjske zavisnosti/usluge, a ne nužno na njenu konceptualnu poziciju u slojevitoj arhitekturi.) Međutim, odnos nije. smanjuje se koliko i ona smjene od komponenti koje su teoretski manje vrijedne do komponenti koje su teoretski vrednije.

Ovo se postiže projektovanjem komponenti čije su eksterne zavisnosti izražene kao interfejs za koji potrošač komponente mora da obezbedi implementaciju. Drugim riječima, određena sučelja izražavaju ono što komponenti treba, a ne kako je koristite (na primjer, "INeedSomething" umjesto "IDoSomething").

Ono na šta se princip inverzije zavisnosti ne odnosi je jednostavna praksa apstrahovanja zavisnosti korišćenjem interfejsa (npr. MyService -> ). Iako ovo razdvaja komponentu od specifičnog detalja implementacije ovisnosti, ne invertuje odnos između potrošača i ovisnosti (na primjer, ⇐ Logger.

Važnost principa inverzije zavisnosti može se svesti na jedan cilj - mogućnost ponovnog korišćenja softverskih komponenti koje se oslanjaju na eksterne zavisnosti za deo svoje funkcionalnosti (registracija, verifikacija, itd.)

Unutar ovog općeg cilja ponovne upotrebe možemo razlikovati dvije podvrste ponovne upotrebe:

    Korišćenje softverske komponente u više aplikacija sa implementacijama zavisnosti (na primer, razvili ste DI kontejner i želite da obezbedite evidenciju, ali ne želite da povežete svoj kontejner sa određenim loggerom, tako da svi koji koriste vaš kontejner takođe moraju da koriste evidenciju biblioteka koju odaberete).

    Korištenje softverskih komponenti u kontekstu koji se razvija (na primjer, razvili ste komponente poslovne logike koje ostaju iste u različitim verzijama aplikacije, gdje se razvijaju detalji implementacije).

U prvom slučaju ponovne upotrebe komponenti u više aplikacija, kao što je infrastrukturna biblioteka, cilj je pružiti potrošačima osnovnu infrastrukturu bez vezivanja vaših potrošača za ovisnosti vaše biblioteke, budući da dobivanje ovisnosti iz takvih ovisnosti zahtijeva da potrošači također zahtijevaju iste zavisnosti. Ovo može biti problematično kada korisnici vaše biblioteke odluče koristiti drugu biblioteku za iste potrebe infrastrukture (kao što su NLog i log4net) ili ako odluče koristiti kasniju verziju potrebne biblioteke koja nije kompatibilna s potrebnom verzijom od strane vaše biblioteke.

U drugom slučaju ponovne upotrebe komponenti poslovne logike (tj. "Komponente višeg nivoa"), cilj je izolirati implementaciju aplikacije u osnovnom području od promjenjivih potreba vaših detalja implementacije (npr. promjena/ažuriranje trajnih biblioteka, razmjena poruka biblioteka) . strategije šifriranja, itd.). U idealnom slučaju, promjena detalja implementacije aplikacije ne bi trebala razbiti komponente koje inkapsuliraju poslovnu logiku aplikacije.

Bilješka. Neki se mogu protiviti opisivanju ovog drugog slučaja kao stvarne ponovne upotrebe, vjerujući da komponente kao što su komponente poslovne logike koje se koriste u jednoj aplikaciji u razvoju predstavljaju samo jednu upotrebu. Ideja ovdje je, međutim, da svaka promjena u detaljima implementacije aplikacije predstavlja novi kontekst i stoga drugačiji slučaj upotrebe, iako se krajnji ciljevi mogu razlikovati kao izolacija i prenosivost.

Iako može biti neke koristi od praćenja principa inverzije zavisnosti u drugom slučaju, treba napomenuti da je njegova važnost primijenjena na moderne jezike kao što su Java i C# uvelike smanjena, možda do te mjere da je irelevantna. Kao što je ranije rečeno, DIP uključuje potpuno odvajanje detalja implementacije u zasebne pakete. U slučaju aplikacije koja se razvija, međutim, jednostavno korištenje sučelja definiranih u smislu poslovnog domena zaštitit će od potrebe za modifikacijom komponenti višeg nivoa zbog promjenjivih potreba komponenti detalja implementacije, čak i ako se detalji implementacije na kraju nalaze u istom paketu. . Ovaj dio principa odražava aspekte koji su bili relevantni za jezik u vrijeme njegove kodifikacije (na primjer, C++), a koji nisu relevantni za novije jezike. Međutim, važnost principa inverzije zavisnosti prvenstveno se odnosi na razvoj softverskih komponenti/biblioteka za višekratnu upotrebu.

Može se pronaći detaljnija rasprava o ovom principu jer se odnosi na jednostavnu upotrebu interfejsa, injekciju zavisnosti i obrazac podeljenog interfejsa.

Kada razvijamo softverske aplikacije, možemo uzeti u obzir klase niskog nivoa - klase koje implementiraju osnovne i primarne operacije (pristup disku, mrežni protokoli, itd.) i klase visokog nivoa - klase koje obuhvataju složenu logiku (poslovni tokovi,...) .

Potonji se oslanjaju na razrede niskog nivoa. Prirodan način implementacije takvih struktura bio bi pisanje klasa niskog nivoa i kad god smo primorani da pišemo složene klase visokog nivoa. Pošto su klase visokog nivoa definisane iz perspektive drugih, čini se da je ovo logičan način da se to uradi. Ali ovo nije responzivni dizajn. Šta se dešava ako trebamo zamijeniti klasu niskog nivoa?

Princip inverzije zavisnosti kaže da:

  • Moduli visokog nivoa ne bi trebali zavisiti od modula niskog nivoa. I jedno i drugo mora zavisiti od apstrakcija.

Ovaj princip ima za cilj da "preokrene" uobičajenu ideju da moduli visokog nivoa u softveru moraju zavisiti od modula nižeg nivoa. Ovdje moduli visokog nivoa posjeduju apstrakciju (na primjer, metode odlučivanja interfejsa) koje implementiraju moduli nižeg nivoa. Dakle, moduli nižeg nivoa zavise od modula višeg nivoa.

Efikasna upotreba inverzije zavisnosti obezbeđuje fleksibilnost i stabilnost kroz arhitekturu vaše aplikacije. Ovo će omogućiti da se vaša aplikacija razvija sigurnije i stabilnije.

Tradicionalna slojevita arhitektura

Tradicionalno, korisnički interfejs slojevite arhitekture zavisio je od poslovnog sloja, koji je zauzvrat zavisio od sloja pristupa podacima.

Morate razumjeti sloj, paket ili biblioteku. Da vidimo kako ide kod.

Imali bismo biblioteku ili paket za sloj pristupa podacima.

// DataAccessLayer.dll javna klasa ProductDAO ( )

// BusinessLogicLayer.dll koristeći DataAccessLayer; javna klasa ProductBO (privatni ProductDAO productDAO; )

Slojevita arhitektura sa inverzijom zavisnosti

Inverzija zavisnosti ukazuje na sledeće:

Moduli visokog nivoa ne bi trebali zavisiti od modula niskog nivoa. I jedno i drugo mora zavisiti od apstrakcija.

Apstrakcije ne bi trebale zavisiti od detalja. Detalji moraju zavisiti od apstrakcija.

Šta su moduli visokog i niskog nivoa? Razmišljajući o modulima kao što su biblioteke ili paketi, moduli visokog nivoa će biti oni koji tradicionalno imaju zavisnosti, a oni niskog nivoa od kojih zavise.

Drugim riječima, visoki nivo modula će biti mjesto gdje se akcija poziva, a niži nivo na kojem se akcija izvršava.

Iz ovog principa može se izvući razuman zaključak: ne bi trebalo da postoji zavisnost između nodula, ali bi trebalo da postoji zavisnost od apstrakcije. Ali prema pristupu koji koristimo, možda pogrešno koristimo zavisnost od ulaganja, ali to je apstrakcija.

Zamislite da prilagodimo naš kod ovako:

Imali bismo biblioteku ili paket za sloj pristupa podacima koji definira apstrakciju.

// DataAccessLayer.dll javno sučelje IProductDAO javna klasa ProductDAO: IProductDAO( )

I druga poslovna logika na razini biblioteke ili paketa koja ovisi o sloju pristupa podacima.

// BusinessLogicLayer.dll koristeći DataAccessLayer; javna klasa ProductBO (privatni IProductDAO productDAO; )

Iako zavisimo od apstrakcije, odnos između poslovanja i pristupa podacima ostaje isti.

Da bi se postigla inverzija zavisnosti, sučelje postojanosti mora biti definirano u modulu ili paketu gdje se nalazi logika ili domena visokog nivoa, a ne u modulu niske razine.

Prvo definirajte što je sloj domene, a apstrakcija njegove komunikacije definirana je postojanošću.

// Domain.dll javno sučelje IProductRepository; koristeći DataAccessLayer; javna klasa ProductBO (privatni IProductRepository productRepository; )

Kada je nivo postojanosti ovisan o domeni, sada je moguće invertirati ako je ovisnost definirana.

// Persistence.dll javna klasa ProductDAO: IProductRepository( )

Produbljivanje principa

Važno je dobro razumjeti koncept, produbljujući svrhu i koristi. Ako ostanemo u mehanici i proučavamo tipično spremište, nećemo moći odrediti gdje možemo primijeniti princip zavisnosti.

Ali zašto invertujemo zavisnost? Koji je glavni cilj osim konkretnih primjera?

Ovo je obično omogućava da se najstabilnije stvari, koje su nezavisne od manje stabilnih, češće mijenjaju.

Lakše je promijeniti tip postojanosti, ili bazu podataka ili tehnologiju za pristup istoj bazi podataka, nego logiku domene ili akcije dizajnirane za komunikaciju s postojanošću. Ovo uzrokuje da se ovisnost preokrene jer je lakše promijeniti postojanost ako se ta promjena dogodi. Na ovaj način nećemo morati mijenjati domenu. Domenski sloj je najstabilniji od svih, tako da ne bi trebao ovisiti ni o čemu.

Ali postoji više od ovog primjera skladištenja. Postoji mnogo scenarija u kojima se ovaj princip primjenjuje, a postoje i arhitekture zasnovane na ovom principu.

arhitektura

Postoje arhitekture u kojima je inverzija zavisnosti ključ njene definicije. U svim domenima ovo je najvažnije, a apstrakcije će specificirati komunikacijski protokol između domene i ostalih paketa ili biblioteka.

Clean Architecture

Za mene je princip inverzije zavisnosti opisan u službenom članku

Problem u C++-u je što datoteke zaglavlja obično sadrže deklaracije privatnih polja i metoda. Dakle, ako C++ modul visokog nivoa sadrži datoteku zaglavlja za modul niskog nivoa, to će zavisiti od stvarnog implementacija detalji ovog modula. A ovo očigledno nije baš dobro. Ali to nije problem u modernijim jezicima koji se danas najčešće koriste.

Moduli visokog nivoa su inherentno manje za ponovnu upotrebu od modula niskog nivoa jer su prvi obično više specifični za aplikaciju/kontekst od drugih. Na primjer, komponenta koja implementira UI ekran je najvišeg nivoa i također je vrlo (u potpunosti?) specifična za aplikaciju. Pokušaj ponovnog korištenja takve komponente u drugoj aplikaciji je kontraproduktivan i može dovesti samo do pretjeranog razvoja.

Stoga, kreiranje zasebne apstrakcije na istoj razini komponente A koja ovisi o komponenti B (koja ne ovisi o A) može se izvršiti samo ako će komponenta A zapravo biti korisna za ponovnu upotrebu u različitim aplikacijama ili kontekstima. Ako to nije slučaj, onda će DIP aplikacija biti loš dizajn.

Jasniji način da se izrazi princip inverzije zavisnosti:

Vaši moduli koji inkapsuliraju složenu poslovnu logiku ne bi trebali direktno ovisiti o drugim modulima koji inkapsuliraju poslovnu logiku. Umjesto toga, trebali bi ovisiti samo o interfejsima za jednostavne podatke.

Odnosno, umjesto implementacije vaše Logic klase kao što ljudi obično rade:

Klasa ovisnost ( ... ) klasa Logika ( privatna ovisnost dep; int doSomething() ( // Poslovna logika koja koristi dep ovdje ) )

trebao bi uraditi nešto poput:

Class Dependency ( ... ) sučelje Data ( ... ) class DataFromDependency implementira Data ( private Dependency dep; ... ) class Logic ( int doSomething(Data data) ( // izračunati nešto s podacima) )

Podaci i DataFromDependency bi trebali živjeti u istom modulu kao i Logic, a ne sa ovisnošću.

Zašto je ovo?

Dobre odgovore i dobre primjere su već dali drugi ovdje.

Smisao inverzije zavisnosti je da se softver učini višekratnim.

Ideja je da se umjesto da se dva dijela koda oslanjaju jedan na drugi, oslanjaju na neki apstraktni interfejs. Tada možete ponovo koristiti bilo koji dio bez drugog.

Ovo se obično postiže invertiranjem kontrolnog kontejnera (IoC) kao što je Spring u Javi. U ovom modelu, svojstva objekata se postavljaju kroz XML konfiguraciju, umjesto da objekti izlaze i pronalaze njihovu ovisnost.

Zamislite ovaj pseudokod...

Javna klasa MyClass (javna usluga myService = ServiceLocator.service; )

MyClass direktno zavisi i od klase Service i od klase ServiceLocator. Ovo je potrebno za oboje ako želite da ga koristite u drugoj aplikaciji. Zamislite sad ovo...

Javna klasa MyClass (javni IService myService;)

MyClass sada koristi jedno sučelje, IService sučelje. Dozvolili bismo da IoC kontejner zapravo postavi vrijednost ove varijable.

Neka postoji hotel koji traži od proizvođača hrane svoje zalihe. Hotel daje naziv hrane (recimo piletina) Generatoru hrane, a Generator vraća traženu hranu u hotel. Ali hotel ne brine o vrsti hrane koju dobija i služi. Tako Generator snabdeva hotel hranom sa oznakom "Hrana".

Ova implementacija u JAVA

FactoryClass sa fabričkom metodom. Generator hrane

Javna klasa FoodGenerator ( Food food; javna hrana getFood(ime stringa)( if(name.equals("fish"))( food = new Fish(); )else if(name.equals("chicken"))( food = new Chicken() else food = null;

Anotacija/Interfejs klase

Javna apstraktna klasa Food ( //Nijedna od podređenih klasa neće nadjačati ovu metodu da bi osigurala kvalitet... public void quality())( String fresh = "Ovo je svježe " + getName(); String tasty = "Ovo je ukusno " + getName(); System.out.println(fresh); System.out.println(tasty); ) javni apstraktni String getName(); )

Piletina prodaje hranu (betonska klasa)

Javna klasa Chicken proširuje hranu ( /*Sve vrste hrane moraju biti svježe i ukusne tako da * Neće nadjačati metodu super klase "property()"*/ public String getName())( return "Chicken"; ) )

Riba prodaje hranu (specifična klasa)

Javna klasa Fish proširuje hranu ( /*Sve vrste hrane moraju biti svježe i ukusne pa * Neće nadjačati metodu super klase "property()"*/ public String getName())( return "Fish"; ) )

Konačno

Hotel

Javna klasa Hotel ( public static void main(String args)( //Korišćenje klase Factory.... FoodGenerator foodGenerator = new FoodGenerator(); //Tvornička metoda za instanciranje hrane... Food food = foodGenerator.getFood( "piletina");

Kao što vidite, hotel ne zna da li je piletina ili riba. Poznato je samo da se radi o prehrambenom artiklu, tj. Hotel zavisi od klase hrane.

Također možete primijetiti da klasa Fish and Chicken implementira klasu Food i nije direktno povezana s hotelom. one. piletina i riba takođe zavise od klase hrane.

To znači da komponenta visokog nivoa (hotel) i komponenta niskog nivoa (riba i piletina) zavise od apstrakcije (hrana).

Ovo se zove inverzija zavisnosti.

Princip inverzije zavisnosti (DIP) to kaže

i) Moduli visokog nivoa ne bi trebali zavisiti od modula niskog nivoa. I jedno i drugo mora zavisiti od apstrakcija.

ii) Apstrakcije nikada ne bi trebalo da zavise od detalja. Detalji moraju zavisiti od apstrakcija.

Javni interfejs ICustomer ( string GetCustomerNameById(int id); ) javna klasa Kupac: ICustomer ( //ctor public Customer()() javni string GetCustomerNameById(int id) ( vrati "Ime lažnog kupca"; ) ) javna klasa CustomerFactory ( public static ICustomer GetCustomerData() ( return new Customer(); ) ) javna klasa CustomerBLL ( ICustomer _customer; public CustomerBLL() ( _customer = CustomerFactory.GetCustomerData(); ) javni string GetCustomerNameById(int idcumer(int id_Customer)(int id_Customer); ) ) program javne klase ( static void Main() ( CustomerBLL customerBLL = new CustomerBLL(); int customerId = 25; string customerName = customerBLL.GetCustomerNameById(customerId); Console.WriteLine(customerName); Console.ReadKey)();

Bilješka. Klasa treba da zavisi od apstrakcija kao što su interfejs ili apstraktne klase, a ne od konkretnih detalja (implementacija interfejsa).

dijeliti

Zapravo, svi principi SOLID Oni su usko povezani jedni s drugima i njihov glavni cilj je pomoći u stvaranju visokokvalitetnog, skalabilnog softvera. Ali poslednji princip SOLID zaista se ističe na njihovoj pozadini. Prvo, pogledajmo formulaciju ovog principa. dakle, princip inverzije zavisnosti (Princip inverzije zavisnosti - DIP): “Zavisnost od apstrakcija. Nema zavisnosti ni od čega konkretnog.”. Poznati specijalista u oblasti razvoja softvera Robert Martin takođe ističe princip DIP i predstavlja ga jednostavno kao rezultat slijeđenja drugih principa SOLID— princip otvoreno/zatvoreno i princip zamjene Liskov. Podsjetimo da prvi kaže da klasu ne treba modificirati radi uvođenja novih promjena, a drugi se tiče nasljeđivanja i pretpostavlja sigurnu upotrebu izvedenih tipova nekog osnovnog tipa bez ometanja ispravnog rada programa. Robert Martin je prvobitno formulirao ovaj princip na sljedeći način:

1). Moduli na višim nivoima ne bi trebali ovisiti o modulima na nižim nivoima. Moduli na oba nivoa moraju zavisiti od apstrakcija.

2). Apstrakcije ne bi trebale zavisiti od detalja. Detalji moraju zavisiti od apstrakcija.

Odnosno, klase se moraju razvijati u smislu apstrakcija, a ne njihovih konkretnih implementacija. I ako slijedite principe OCP I LSP, onda je to upravo ono što ćemo postići. Pa da se vratimo malo na lekciju o tome. Tu smo, kao primjer, razmotrili klasu Bard, koja je na samom početku bila strogo vezana za čas gitara, koji predstavlja određeni muzički instrument:

javna klasa Bard ( privatna gitarska gitara; javni Bard (gitara gitara) ( this.guitar = gitara; ) public void play() ( guitar.play(); ) )

javni razred Bard (

privatna gitarska gitara;

javni bard (gitara gitara)

ovo. gitara = gitara;

javna praznina igra()

gitara play();

Ako bismo ovoj klasi htjeli dodati podršku za druge muzičke instrumente, morali bismo ovu klasu modificirati na ovaj ili onaj način. Ovo je jasno kršenje principa OCP. A možda ste već primijetili da se radi i o kršenju principa DIP, pošto se u našem slučaju pokazalo da naša apstrakcija zavisi od detalja. Sa stanovišta daljeg širenja naše klase, to nije nimalo dobro. Tako da naš razred ispunjava uslove principa OCP sistemu smo dodali interfejs Instrument, koju su realizovale specifične klase koje predstavljaju određene vrste muzičkih instrumenata.

File Instrument.java:

javni interfejs instrument ( void play(); )

javni interfejs instrument (

void play();

File Guitar.java:

class Gitara implementira instrument( @Override public void play() ( System.out.println("Sviraj gitaru!"); ) )

klasa Instrumenti za gitaru Instrument (

@Override

javna praznina igra()

Sistem. out . println("Sviraj gitaru!");

File Lute.java:

javna klasa lutnje implementira instrument( @Override public void play() ( System.out.println("Pusti lutnju!"); ) )

javna klasa lutnja implementira instrument (

@Override

javna praznina igra()

Sistem. out . println("Sviraj lutnju!");

Nakon toga smo promijenili razred Bard, tako da po potrebi možemo zamijeniti implementacije upravo onima koje su nam potrebne. Ovo uvodi dodatnu fleksibilnost u kreirani sistem i smanjuje njegovu spregu (jake zavisnosti klasa jedna od druge).

javna klasa Bard ( privatni instrument instrument; javni Bard() ( ) public void play() ( instrument.play(); ) javni void setInstrument(instrument instrument) ( this.instrument = instrument; ) )

javni razred Bard (

privatni instrument instrument ;

Povratak

×
Pridružite se zajednici nloeda.ru!
U kontaktu sa:
Već sam pretplaćen na zajednicu “nloeda.ru”