CodeGym/Curs Java/Modulul 3/Inversarea dependenței

Inversarea dependenței

Disponibil

9.1 Inversarea dependenței

Amintiți-vă, am spus odată că într-o aplicație server nu puteți crea fluxuri prin intermediul new Thread().start()? Doar containerul ar trebui să creeze fire. Acum vom dezvolta această idee și mai mult.

Toate obiectele ar trebui, de asemenea, create numai de container . Desigur, nu vorbim despre toate obiectele, ci mai degrabă despre așa-numitele obiecte de afaceri. Ele sunt adesea denumite și coșuri de gunoi. Picioarele acestei abordări cresc de la al cincilea principiu al SOLID, care necesită eliminarea claselor și trecerea la interfețe:

  • Modulele de nivel superior nu ar trebui să depindă de modulele de nivel inferior. Atât acestea, cât și altele ar trebui să depindă de abstracții.
  • Abstracțiile nu ar trebui să depindă de detalii. Implementarea trebuie să depindă de abstractizare.

Modulele nu trebuie să conțină referințe la implementări specifice, iar toate dependențele și interacțiunile dintre ele ar trebui să fie construite exclusiv pe baza abstracțiilor (adică, interfețe). Esența acestei reguli poate fi scrisă într-o singură frază: toate dependențele trebuie să fie sub formă de interfețe .

În ciuda naturii sale fundamentale și a simplității aparente, această regulă este încălcată cel mai adesea. Și anume, de fiecare dată când folosim noul operator în codul programului/modulului și creăm un nou obiect de un anumit tip, astfel, în loc să depindem de interfață, se formează dependența de implementare.

Este clar că acest lucru nu poate fi evitat și obiectele trebuie create undeva. Dar, cel puțin, trebuie să minimizați numărul de locuri în care se face acest lucru și în care clasele sunt specificate în mod explicit, precum și să localizați și să izolați astfel de locuri, astfel încât să nu fie împrăștiate în codul programului.

O soluție foarte bună este ideea nebună de a concentra crearea de noi obiecte în cadrul obiectelor și modulelor specializate - fabrici, locatoare de servicii, containere IoC.

Într-un anumit sens, o astfel de decizie urmează principiul alegerii unice, care spune: „De câte ori un sistem software trebuie să suporte mai multe alternative, lista lor completă ar trebui să fie cunoscută doar de un singur modul al sistemului” .

Prin urmare, dacă în viitor este necesar să adăugați noi opțiuni (sau noi implementări, ca în cazul creării de noi obiecte pe care le luăm în considerare), atunci va fi suficient să actualizați doar modulul care conține aceste informații și toate celelalte module. vor rămâne neafectate și își vor putea continua munca.ca de obicei.

Exemplul 1

new ArrayList În loc să scrieți ceva de genul , ar fi logic List.new()ca JDK să vă ofere implementarea corectă a unei foi: ArrayList, LinkedList sau chiar ConcurrentList.

De exemplu, compilatorul vede că există apeluri la obiect din fire diferite și pune acolo o implementare sigură pentru fire. Sau prea multe inserții în mijlocul foii, atunci implementarea se va baza pe LinkedList.

Exemplul 2

Acest lucru sa întâmplat deja cu feluri, de exemplu. Când ați scris ultima dată un algoritm de sortare pentru a sorta o colecție? În schimb, acum toată lumea folosește metoda Collections.sort(), iar elementele colecției trebuie să accepte interfața Comparable (comparabil).

Dacă sort()treceți o colecție de mai puțin de 10 elemente metodei, este foarte posibil să o sortați cu o sortare cu bule (Bubble sort), și nu cu Quicksort.

Exemplul 3

Compilatorul urmărește deja cum concatenați șirurile și vă va înlocui codul cu StringBuilder.append().

9.2 Inversarea dependenței în practică

Acum, cel mai interesant: să ne gândim cum putem combina teoria și practica. Cum pot modulele să creeze și să primească corect „dependențele” lor și să nu încalce inversarea dependenței?

Pentru a face acest lucru, atunci când proiectați un modul, trebuie să decideți singur:

  • ce face modulul, ce funcție îndeplinește;
  • atunci modulul are nevoie din mediul său, adică cu ce obiecte/module va trebui să se ocupe;
  • Și cum o va obține?

Pentru a respecta principiile inversării dependenței, cu siguranță trebuie să decideți ce obiecte externe folosește modulul dvs. și cum va obține referințe la acestea.

Și iată următoarele opțiuni:

  • modulul însuși creează obiecte;
  • modulul preia obiecte din container;
  • modulul habar n-are de unde provin obiectele.

Problema este că pentru a crea un obiect, trebuie să apelați un constructor de un anumit tip și, ca urmare, modulul va depinde nu de interfață, ci de implementarea specifică. Dar dacă nu dorim ca obiectele să fie create explicit în codul modulului, atunci putem folosi modelul Factory Method .

„Concluzia este că, în loc să instanțiem direct un obiect prin new, oferim clasei client o interfață pentru a crea obiecte. Deoarece o astfel de interfață poate fi întotdeauna înlocuită cu designul potrivit, obținem o oarecare flexibilitate atunci când folosim module de nivel scăzut. în module de nivel înalt” .

În cazurile în care este necesar să se creeze grupuri sau familii de obiecte înrudite, se folosește o fabrică abstractă în locul unei metode de fabrică .

9.3 Utilizarea Localizatorului de servicii

Modulul preia obiectele necesare de la cel care le are deja. Se presupune că sistemul are un depozit de obiecte, în care modulele își pot „pune” obiectele și „lua” obiecte din depozit.

Această abordare este implementată de modelul Service Locator , a cărui idee principală este că programul are un obiect care știe să obțină toate dependențele (serviciile) care pot fi necesare.

Principala diferență față de fabrici este că Service Locator nu creează obiecte, ci de fapt conține deja obiecte instanțiate (sau știe de unde / cum să le obțină, iar dacă creează, atunci o singură dată la primul apel). Fabrica la fiecare apel creează un nou obiect despre care obțineți proprietatea deplină și puteți face orice doriți cu el.

Important ! Localizatorul de servicii produce referințe la aceleași obiecte deja existente . Prin urmare, trebuie să fii foarte atent cu obiectele emise de Service Locator, deoarece altcineva le poate folosi în același timp cu tine.

Obiectele din Service Locator pot fi adăugate direct prin fișierul de configurare și, într-adevăr, în orice mod convenabil pentru programator. Localizatorul de servicii în sine poate fi o clasă statică cu un set de metode statice, un singleton sau o interfață și poate fi trecut la clasele necesare printr-un constructor sau o metodă.

Service Locator este uneori numit anti-pattern și este descurajat (pentru că creează conexiuni implicite și dă doar aspectul unui design bun). Puteți citi mai multe de la Mark Seaman:

9.4 Injecția de dependență

Modulului nu-i pasă deloc de dependențele „minării”. Determină doar ce are nevoie pentru a funcționa, iar toate dependențele necesare sunt furnizate (introduse) din exterior de altcineva.

Aceasta este ceea ce se numește - Injecția de dependență . De obicei, dependențele necesare sunt transmise fie ca parametri de constructor (Constructor Injection), fie prin metode de clasă (Setter injection).

Această abordare inversează procesul de creare a dependențelor - în loc de modulul în sine, crearea dependențelor este controlată de cineva din exterior. Modulul de la emițătorul activ de obiecte devine pasiv - nu el creează, ci alții creează pentru el.

Această schimbare de direcție se numește Inversarea Controlului sau Principiul Hollywood - „Nu ne sunați, vă vom chema”.

Aceasta este soluția cea mai flexibilă, oferind modulelor cea mai mare autonomie . Putem spune că doar acesta implementează pe deplin „Principiul responsabilității unice” – modulul ar trebui să fie complet concentrat pe a-și face treaba bine și să nu-și facă griji pentru nimic altceva.

Furnizarea modulului cu tot ceea ce este necesar pentru lucru este o sarcină separată, care ar trebui să fie gestionată de „specialistul” corespunzător (de obicei un anumit container, un container IoC, este responsabil pentru gestionarea dependențelor și implementarea acestora).

De fapt, aici totul este ca în viață: într-o companie bine organizată, programul de programare, iar birourile, calculatoarele și tot ce le trebuie pentru muncă sunt cumpărate și furnizate de managerul biroului. Sau, dacă utilizați metafora programului ca constructor, modulul nu ar trebui să se gândească la fire, altcineva este implicat în asamblarea constructorului, și nu piesele în sine.

Nu ar fi o exagerare să spunem că utilizarea interfețelor pentru a descrie dependențele dintre module (Dependency Inversion) + crearea și injectarea corectă a acestor dependențe (în primul rând Dependency Injection) sunt tehnici cheie pentru decuplare .

Ele servesc drept fundație pe care cuplarea liberă a codului, flexibilitatea acestuia, rezistența la modificări, reutilizare și fără de care toate celelalte tehnici au puțin sens. Aceasta este fundamentul cuplajului liber și al arhitecturii bune.

Principiul inversării controlului (împreună cu Dependency Injection și Service Locator) este discutat în detaliu de Martin Fowler. Există traduceri ale ambelor articole ale sale: „Inversarea containerelor de control și modelul de injectare a dependenței” și „Inversarea controlului” .

Comentarii
  • Popular
  • Nou
  • Vechi
Trebuie să fii conectat pentru a lăsa un comentariu
Această pagină nu are încă niciun comentariu