9.1 Dependency Inversion

Tandaan, minsan naming sinabi na sa isang server application ay hindi ka basta basta makakagawa ng mga stream sa pamamagitan ng new Thread().start()? Ang lalagyan lang ang dapat gumawa ng mga thread. Mas lalo pa nating bubuuin ang ideyang ito.

Ang lahat ng mga bagay ay dapat ding likhain lamang ng lalagyan . Siyempre, hindi natin pinag-uusapan ang lahat ng bagay, ngunit sa halip ay tungkol sa tinatawag na mga bagay sa negosyo. Madalas din silang tinutukoy bilang mga basurahan. Ang mga binti ng diskarteng ito ay lumalaki mula sa ikalimang prinsipyo ng SOLID, na nangangailangan ng pag-alis ng mga klase at paglipat sa mga interface:

  • Ang mga top-level na module ay hindi dapat nakadepende sa lower-level na mga module. Parehong iyon, at iba pa ay dapat na nakasalalay sa mga abstraction.
  • Ang mga abstraction ay hindi dapat nakadepende sa mga detalye. Ang pagpapatupad ay dapat depende sa abstraction.

Ang mga module ay hindi dapat maglaman ng mga sanggunian sa mga partikular na pagpapatupad, at lahat ng mga dependency at pakikipag-ugnayan sa pagitan ng mga ito ay dapat na binuo lamang batay sa mga abstraction (iyon ay, mga interface). Ang pinakabuod ng panuntunang ito ay maaaring isulat sa isang parirala: ang lahat ng mga dependency ay dapat nasa anyo ng mga interface .

Sa kabila ng pangunahing katangian nito at maliwanag na pagiging simple, ang panuntunang ito ay madalas na nilalabag. Lalo na, sa bawat oras na kapag ginagamit namin ang bagong operator sa code ng programa/module at lumikha ng isang bagong bagay ng isang tiyak na uri, kaya, sa halip na depende sa interface, ang pag-asa sa pagpapatupad ay nabuo.

Malinaw na hindi ito maiiwasan at dapat gumawa ng mga bagay sa isang lugar. Ngunit, hindi bababa sa, kailangan mong bawasan ang bilang ng mga lugar kung saan ito ginagawa at kung saan ang mga klase ay tahasang tinukoy, pati na rin i-localize at ihiwalay ang mga naturang lugar upang hindi sila nakakalat sa buong code ng programa.

Ang isang napakahusay na solusyon ay ang nakatutuwang ideya ng pag-concentrate sa paglikha ng mga bagong bagay sa loob ng mga espesyal na bagay at module - mga pabrika, tagahanap ng serbisyo, mga lalagyan ng IoC.

Sa isang kahulugan, ang naturang desisyon ay sumusunod sa Single Choice Principle, na nagsasabing: "Sa tuwing ang isang software system ay dapat na sumusuporta sa maraming alternatibo, ang kanilang kumpletong listahan ay dapat na malaman lamang sa isang module ng system" .

Samakatuwid, kung sa hinaharap ay kinakailangan upang magdagdag ng mga bagong pagpipilian (o mga bagong pagpapatupad, tulad ng sa kaso ng paglikha ng mga bagong bagay na aming isinasaalang-alang), kung gayon ito ay sapat na upang i-update lamang ang module na naglalaman ng impormasyong ito, at lahat ng iba pang mga module. ay mananatiling hindi maaapektuhan at maipagpapatuloy ang kanilang trabaho.gaya ng dati.

Halimbawa 1

new ArrayList Sa halip na magsulat ng isang bagay tulad ng , makatuwiran List.new()para sa JDK na magbigay sa iyo ng tamang pagpapatupad ng isang dahon: ArrayList, LinkedList, o kahit na ConcurrentList.

Halimbawa, nakikita ng compiler na may mga tawag sa object mula sa iba't ibang mga thread at naglalagay ng isang thread-safe na pagpapatupad doon. O masyadong maraming pagsingit sa gitna ng sheet, pagkatapos ay ibabatay ang pagpapatupad sa LinkedList.

Halimbawa 2

Ito ay nangyari na sa mga uri, halimbawa. Kailan ka huling sumulat ng algorithm sa pag-uuri upang pagbukud-bukurin ang isang koleksyon? Sa halip, ngayon ay ginagamit ng lahat ang pamamaraan Collections.sort(), at ang mga elemento ng koleksyon ay dapat na sumusuporta sa Maihahambing na interface (maihahambing).

Kung sort()magpapasa ka ng isang koleksyon ng mas mababa sa 10 elemento sa pamamaraan, ito ay lubos na posible na pag-uri-uriin ito gamit ang bubble sort (Bubble sort), at hindi Quicksort.

Halimbawa 3

Pinapanood na ng compiler kung paano mo pinagsasama-sama ang mga string at papalitan ang iyong code ng StringBuilder.append().

9.2 Dependency inversion sa pagsasanay

Ngayon ang pinaka-kawili-wili: isipin natin kung paano natin pagsasamahin ang teorya at kasanayan. Paano magagawa at matatanggap ng mga module nang tama ang kanilang "mga dependency" at hindi lalabag sa Dependency Inversion?

Upang gawin ito, kapag nagdidisenyo ng isang module, dapat kang magpasya para sa iyong sarili:

  • kung ano ang ginagawa ng module, kung ano ang function na ginagawa nito;
  • pagkatapos ay kailangan ng module mula sa kapaligiran nito, iyon ay, kung anong mga bagay / module ang kailangan nitong harapin;
  • At paano niya ito makukuha?

Upang makasunod sa mga prinsipyo ng Dependency Inversion, tiyak na kailangan mong magpasya kung aling mga panlabas na bagay ang ginagamit ng iyong module at kung paano ito makakakuha ng mga sanggunian sa kanila.

At narito ang mga sumusunod na pagpipilian:

  • ang module mismo ay lumilikha ng mga bagay;
  • ang module ay kumukuha ng mga bagay mula sa lalagyan;
  • walang ideya ang modyul kung saan nanggaling ang mga bagay.

Ang problema ay upang lumikha ng isang bagay, kailangan mong tumawag sa isang tagabuo ng isang tiyak na uri, at bilang isang resulta, ang module ay hindi nakasalalay sa interface, ngunit sa tiyak na pagpapatupad. Ngunit kung hindi namin gustong gumawa ng mga bagay nang tahasan sa code ng module, maaari naming gamitin ang pattern ng Factory Method .

"Ang bottom line ay na sa halip na direktang mag-instantiate ng isang bagay sa pamamagitan ng bago, binibigyan namin ang klase ng kliyente ng ilang interface upang lumikha ng mga bagay. Dahil ang ganoong interface ay maaaring palaging ma-override ng tamang disenyo, nakakakuha kami ng ilang flexibility kapag gumagamit ng mga low-level na module sa mga high-level na module" .

Sa mga kaso kung saan kinakailangan na lumikha ng mga grupo o pamilya ng mga kaugnay na bagay, isang Abstract na factory ang ginagamit sa halip na isang Factory Method .

9.3 Paggamit ng Service Locator

Kinukuha ng module ang mga kinakailangang bagay mula sa isa na mayroon na nito. Ipinapalagay na ang system ay may ilang imbakan ng mga bagay, kung saan ang mga module ay maaaring "ilagay" ang kanilang mga bagay at "kumuha" ng mga bagay mula sa imbakan.

Ang diskarte na ito ay ipinatupad ng pattern ng Service Locator , ang pangunahing ideya kung saan ay ang programa ay may isang bagay na nakakaalam kung paano makuha ang lahat ng mga dependency (mga serbisyo) na maaaring kailanganin.

Ang pangunahing pagkakaiba mula sa mga pabrika ay ang Service Locator ay hindi gumagawa ng mga bagay, ngunit aktwal na naglalaman ng mga instantiated na bagay (o alam kung saan / kung paano makuha ang mga ito, at kung ito ay lumilikha, pagkatapos ay isang beses lamang sa unang tawag). Ang pabrika sa bawat tawag ay lumilikha ng bagong bagay na ganap mong pagmamay-ari at magagawa mo ang anumang gusto mo dito.

Mahalaga ! Ang tagahanap ng serbisyo ay gumagawa ng mga sanggunian sa parehong mayroon nang mga bagay . Samakatuwid, kailangan mong maging maingat sa mga bagay na ibinigay ng Service Locator, dahil maaaring gamitin ng ibang tao ang mga ito nang kasabay mo.

Ang mga bagay sa Service Locator ay maaaring direktang idagdag sa pamamagitan ng configuration file, at sa anumang paraan na maginhawa para sa programmer. Ang Service Locator mismo ay maaaring isang static na klase na may set ng mga static na pamamaraan, isang singleton, o isang interface, at maaaring ipasa sa mga kinakailangang klase sa pamamagitan ng isang constructor o pamamaraan.

Ang Tagahanap ng Serbisyo ay minsan tinatawag na isang anti-pattern at nasiraan ng loob (dahil lumilikha ito ng mga implicit na koneksyon at nagbibigay lamang ng hitsura ng magandang disenyo). Maaari kang magbasa ng higit pa mula kay Mark Seaman:

9.4 Dependency Injection

Ang module ay walang pakialam sa mga dependency na "pagmimina". Tinutukoy lamang nito kung ano ang kailangan nitong gumana, at lahat ng kinakailangang dependency ay ibinibigay (ipinakilala) mula sa labas ng ibang tao.

Ito ang tinatawag na - Dependency Injection. Karaniwan, ang mga kinakailangang dependency ay ipinapasa alinman bilang mga parameter ng constructor (Constructor Injection) o sa pamamagitan ng mga pamamaraan ng klase (Setter injection).

Binabaligtad ng diskarteng ito ang proseso ng paglikha ng mga dependency - sa halip na ang module mismo, ang paglikha ng mga dependency ay kinokontrol ng isang tao mula sa labas. Ang module mula sa aktibong emitter ng mga bagay ay nagiging passive - hindi siya ang lumilikha, ngunit ang iba ay lumikha para sa kanya.

Ang pagbabagong ito sa direksyon ay tinatawag na Inversion of Control , o ang Hollywood Principle - "Huwag mo kaming tawagan, tatawagan ka namin."

Ito ang pinaka-flexible na solusyon, na nagbibigay sa mga module ng pinakamalaking awtonomiya . Masasabi natin na ito lamang ang ganap na nagpapatupad ng "Single Responsibility Principle" - ang modyul ay dapat na ganap na nakatutok sa paggawa ng maayos sa trabaho nito at hindi nag-aalala tungkol sa anumang bagay.

Ang pagbibigay sa module ng lahat ng kailangan para sa trabaho ay isang hiwalay na gawain, na dapat pangasiwaan ng naaangkop na "espesyalista" (karaniwan ay isang tiyak na lalagyan, isang lalagyan ng IoC, ang may pananagutan sa pamamahala ng mga dependency at ang kanilang pagpapatupad).

Sa katunayan, ang lahat ng bagay dito ay tulad ng sa buhay: sa isang maayos na kumpanya, programmer program, at ang mga mesa, mga computer at lahat ng kailangan nila para sa trabaho ay binili at ibinigay ng manager ng opisina. O, kung gagamitin mo ang metapora ng programa bilang isang constructor, ang module ay hindi dapat mag-isip tungkol sa mga wire, ibang tao ang kasangkot sa pag-assemble ng constructor, at hindi ang mga bahagi mismo.

Hindi isang pagmamalabis na sabihin na ang paggamit ng mga interface upang ilarawan ang mga dependency sa pagitan ng mga module (Dependency Inversion) + ang tamang paglikha at pag-iniksyon ng mga dependency na ito (pangunahin ang Dependency Injection) ay mga pangunahing pamamaraan para sa decoupling .

Ang mga ito ay nagsisilbing pundasyon kung saan ang maluwag na pagkakabit ng code, ang kakayahang umangkop nito, paglaban sa mga pagbabago, muling paggamit, at kung wala ang lahat ng iba pang mga diskarte ay walang kabuluhan. Ito ang pundasyon ng maluwag na pagkabit at magandang arkitektura.

Ang prinsipyo ng Inversion of Control (kasama ang Dependency Injection at Service Locator) ay tinalakay nang detalyado ni Martin Fowler. May mga pagsasalin ng pareho sa kanyang mga artikulo: "Inversion of Control Containers and the Dependency Injection pattern" at "Inversion of Control" .