Pagbabaligtad ng dependency. Isang kritikal na pagtingin sa prinsipyo ng dependency inversion. Tradisyunal na Layered Architecture

Sa katunayan, ang lahat ng mga prinsipyo SOLID Ang mga ito ay malapit na nauugnay sa isa't isa at ang kanilang pangunahing layunin ay upang makatulong na lumikha ng mataas na kalidad, nasusukat na software. Ngunit ang huling prinsipyo SOLID talagang namumukod-tangi laban sa kanilang background. Una, tingnan natin ang pagbabalangkas ng prinsipyong ito. Kaya, prinsipyo ng dependency inversion (Dependency Inversion Principle - DIP): “Dependence sa abstractions. Walang pag-asa sa anumang partikular na bagay.". Ang kilalang espesyalista sa larangan ng software development, si Robert Martin, ay binibigyang-diin din ang prinsipyo DIP at inilalahad ito bilang resulta lamang ng pagsunod sa iba pang mga prinsipyo SOLID— ang bukas/sarado na prinsipyo at ang Liskov substitution principle. Alalahanin na ang una ay nagsasabi na ang klase ay hindi dapat baguhin upang ipakilala ang mga bagong pagbabago, at ang pangalawa ay may kinalaman sa pamana at ipinapalagay ang ligtas na paggamit ng mga nagmula na uri ng ilang uri ng base nang hindi nakakasagabal sa tamang operasyon ng programa. Si Robert Martin ang orihinal na nagbalangkas ng prinsipyong ito sa sumusunod na paraan:

1). Ang mga module sa mas mataas na antas ay hindi dapat nakadepende sa mga module sa mas mababang antas. Ang mga module sa parehong antas ay dapat nakadepende sa mga abstraction.

2). Ang mga abstraction ay hindi dapat nakadepende sa mga detalye. Ang mga detalye ay dapat nakadepende sa mga abstraction.

Iyon ay, ang mga klase ay kailangang mabuo sa mga tuntunin ng abstraction, at hindi ang kanilang mga kongkretong pagpapatupad. At kung susundin mo ang mga prinsipyo OCP At LSP, kung gayon ito mismo ang ating makakamit. Kaya bumalik tayo ng kaunti sa aralin sa. Doon, bilang isang halimbawa, isinasaalang-alang namin ang klase Bard, na sa simula pa lang ay mahigpit na nakatali sa klase Gitara, na kumakatawan sa isang partikular na instrumentong pangmusika:

pampublikong klase Bard ( private Guitar guitar; public Bard(Guitar guitar) ( this.guitar = guitar; ) public void play() ( guitar.play(); ) )

pampublikong klase Bard (

pribadong Gitara gitara;

pampublikong Bard (Gitara na gitara)

ito. gitara = gitara ;

pampublikong void play()

gitara play();

Kung gusto naming magdagdag ng suporta para sa iba pang mga instrumentong pangmusika sa klase na ito, kailangan naming baguhin ang klase na ito sa isang paraan o iba pa. Ito ay isang malinaw na paglabag sa prinsipyo OCP. At maaaring napansin mo na ang mga ito ay mga paglabag din sa prinsipyo DIP, dahil sa aming kaso ang aming abstraction ay nakadepende sa mga detalye. Mula sa punto ng view ng karagdagang pagpapalawak ng aming klase, ito ay hindi lahat ng mabuti. Upang matugunan ng aming klase ang mga kondisyon ng prinsipyo OCP nagdagdag kami ng interface sa system Instrumento, na ipinatupad ng mga partikular na klase na kumakatawan sa ilang uri ng mga instrumentong pangmusika.

file Instrument.java:

Instrumentong pampublikong interface ( void play(); )

Instrumentong pampublikong interface (

void play();

file Guitar.java:

class Guitar implements Instrument( @Override public void play() ( System.out.println("Play Guitar!"); ) )

class Guitar implements Instrument (

@I-override

pampublikong void play()

Sistema. labas . println("Maglaro ng Gitara!");

file Lute.java:

ang pampublikong klase na Lute ay nagpapatupad ng Instrument( @Override public void play() ( System.out.println("Play Lute!"); ) )

Ang pampublikong klaseng Lute ay nagpapatupad ng Instrument (

@I-override

pampublikong void play()

Sistema. labas . println("Maglaro ng Lute!");

Pagkatapos nun ay nagpalit na kami ng klase Bard, upang, kung kinakailangan, maaari nating palitan ang mga pagpapatupad ng eksaktong kailangan natin. Ito ay nagpapakilala ng karagdagang flexibility sa nilikhang sistema at binabawasan ang pagkabit nito (malakas na dependency ng mga klase sa isa't isa).

pampublikong klase Bard ( pribadong Instrumentong instrumento; pampublikong Bard() ( ) pampublikong void play() ( instrument.play(); ) public void setInstrument(Instrumentong instrumento) ( this.instrument = instrument; ) )

pampublikong klase Bard (

pribadong Instrumentong instrumento ;

Ang dependency inversion ay isa sa pinakamahalagang programming idioms. Nakakagulat na kakaunti ang mga paglalarawan ng idyoma (prinsipyo) na ito sa Internet sa wikang Ruso. Kaya nagpasya akong subukang gumawa ng isang paglalarawan. Gagawa ako ng mga halimbawa sa Java, sa sa sandaling ito Ito ay mas madali para sa akin, kahit na ang prinsipyo ng dependency inversion ay nalalapat sa anumang programming language.

Ang paglalarawang ito ay binuo kasama ni Vladimir Matveev bilang paghahanda para sa mga klase sa mga mag-aaral na nag-aaral ng Java.

Iba pang mga artikulo mula sa seryeng ito:

Hayaan akong magsimula sa kahulugan ng "addiction." Ano ang addiction? Kung ang iyong code ay gumagamit ng ilang klase sa loob o tahasang tumatawag sa isang static na paraan ng ilang klase o function, isa itong dependency. Hayaan akong ipaliwanag sa mga halimbawa:

Sa ibaba, ang class A, sa loob ng isang method na tinatawag na someMethod(), ay tahasang lumilikha ng object ng class B at tinatawag ang method nito someMethodOfB()

Pampublikong klase A ( void someMethod() ( B b = new B(); b.someMethodOfB(); ) )

Katulad nito, halimbawa, tahasang ina-access ng class B ang mga static na field at pamamaraan ng klase ng System:

Pampublikong klase B ( void someMethodOfB() ( System.out.println("Hello world"); ) )

Sa lahat ng kaso, kapag ang anumang klase (uri A) ay nakapag-iisa na lumikha ng anumang klase (uri B) o tahasang nag-access ng mga static na field o miyembro ng klase, ito ay tinatawag tuwid pagkagumon. Yung. mahalaga: kung ang isang klase sa loob mismo ay gumagana sa loob mismo ng isa pang klase, ito ay isang dependency. Kung ito rin ay lumilikha ng klase sa loob mismo, kung gayon ito tuwid pagkagumon.

Ano ang mali sa mga direktang dependency? Ang mga direktang dependency ay masama dahil ang isang klase na independiyenteng lumikha ng isa pang klase sa loob mismo ay "mahigpit" na nakatali sa klase na ito. Yung. kung tahasang nakasulat na B = new B(); , pagkatapos ang class A ay palaging gagana sa klase B at walang ibang klase. O kung ito ay nagsasabing System.out.println("..."); pagkatapos ay ang klase ay palaging output sa System.out at wala saanman.

Para sa maliliit na klase, ang mga dependency ay hindi kakila-kilabot. Maaaring gumana nang maayos ang code na ito. Ngunit sa ilang mga kaso, upang ang iyong klase A ay maaaring gumana sa pangkalahatan sa kapaligiran ng iba't ibang mga klase, maaaring mangailangan ito ng iba pang mga pagpapatupad ng mga klase - dependencies. Yung. Halimbawa, kakailanganin mo hindi klase B, ngunit isa pang klase na may parehong interface, o hindi System.out, ngunit halimbawa, output sa isang logger (halimbawa log4j).

Ang direktang relasyon ay maaaring graphical na ipakita tulad ng sumusunod:

Yung. kapag gumawa ka ng class A sa iyong code: A a = new A(); sa katunayan, hindi lamang isang klase A ang nilikha, ngunit isang buong hierarchy ng mga umaasa na klase, isang halimbawa nito ay nasa larawan sa ibaba. Ang hierarchy na ito ay "matibay": nang hindi binabago ang source code ng mga indibidwal na klase, hindi mo maaaring palitan ang alinman sa mga klase sa hierarchy. Samakatuwid, ang klase A sa naturang pagpapatupad ay hindi gaanong naaangkop sa isang nagbabagong kapaligiran. Malamang, hindi ito magagamit sa anumang code maliban sa partikular na code kung saan mo isinulat ito.

Upang ihiwalay ang klase A mula sa mga partikular na dependencies, gamitin dependency injection. Ano ang dependency injection? Sa halip na tahasang lumikha ng kinakailangang klase sa code, ang mga dependency ay ipinapasa sa klase A sa pamamagitan ng constructor:

Pampublikong klase A ( pribadong huling B b; pampublikong A(B b) ( ito.b = b; ) pampublikong walang bisa someMethod() ( b.someMethodOfB(); ) )

yun. Natatanggap na ngayon ng class A ang dependency nito sa pamamagitan ng constructor. Ngayon, upang lumikha ng klase A, kakailanganin mo munang lumikha ng nakadependeng klase nito. SA sa kasong ito ito ay B:

B b = bagong B(); A a = bagong A(b); a.someMethod();

Kung ang parehong pamamaraan ay paulit-ulit para sa lahat ng mga klase, i.e. ipasa ang isang instance ng class D sa constructor ng class B, ang dependencies nito E at F sa constructor ng class D, atbp., pagkatapos ay makakakuha ka ng code kung saan ang lahat ng dependencies ay nilikha sa reverse order:

G g = bagong G(); H h = bagong H(); F f = bago (g,h); E e = bagong E(); D d = bagong D(e,f); B b = bagong B(d); A a = bagong A(b); a.someMethod();

Ito ay maaaring ipakita sa graphic na paraan tulad nito:

Kung ihahambing mo ang 2 larawan - ang larawan sa itaas na may mga direktang dependency at ang pangalawang larawan na may dependency injection - makikita mo na ang direksyon ng mga arrow ay nagbago sa kabaligtaran. Para sa kadahilanang ito, ang idyoma ay tinatawag na "pagbabaligtad" ng mga dependencies. Sa madaling salita, ang dependency inversion ay nangangahulugan na ang klase ay hindi gumagawa ng mga dependency sa sarili nitong, ngunit natatanggap ang mga ito sa nilikha na form sa constructor (o kung hindi man).

Bakit mabuti ang dependency inversion? Sa dependency inversion, maaari mong palitan ang lahat ng dependencies sa isang klase nang hindi binabago ang code nito. Nangangahulugan ito na ang iyong klase A ay maaaring madaling i-configure para magamit sa ibang programa maliban sa isa kung saan ito orihinal na isinulat. yun. Ang prinsipyo ng dependency inversion (minsan ay tinatawag na prinsipyo ng dependency injection) ay susi sa pagbuo ng flexible, modular, reusable code.

Ang kawalan ng dependency injection ay makikita din sa unang tingin - ang mga bagay ng mga klase na idinisenyo gamit ang pattern na ito ay labor-intensive sa paggawa. Samakatuwid, ang dependency injection ay karaniwang ginagamit kasabay ng ilang library na idinisenyo upang mapadali ang gawaing ito. Halimbawa, isa sa mga library ng Google Guice. Cm.

2 sagot

Magandang tanong - ang salitang inversion ay medyo nakakagulat (dahil pagkatapos ilapat ang DIP , ang mas mababang antas ng dependency module ay halatang hindi na nakadepende ngayon sa caller module. mataas na lebel: ang tumatawag man o ang umaasa ay mas maluwag na ngayong pinagsama sa pamamagitan ng karagdagang abstraction).

Maaaring may magtanong kung bakit ginagamit ko ang salitang "inversion". Upang maging patas, ito ay dahil mas tradisyunal na paraan ng pagbuo ng software, tulad ng structured analysis at disenyo, ay may posibilidad na lumikha ng mga istruktura ng software kung saan ang mga high-level na module ay nakadepende sa mga low-level na module at kung saan ang mga abstraction ay nakasalalay sa mga detalye. Sa katunayan, isa sa mga layunin ng mga pamamaraang ito ay tukuyin ang isang subroutine hierarchy na naglalarawan kung paano tumatawag ang mga high-level na module sa mga low-level na module.... Kaya, ang dependency structure ng isang well-designed object-oriented program ay " baligtad" na nauugnay sa istraktura ng dependency na karaniwang resulta ng mga tradisyonal na pamamaraang pamamaraan.

Isang puntong dapat tandaan kapag nagbabasa ng papel ni Uncle Bob sa DIP ay ang C++ ay walang mga interface (at sa oras ng pagsulat, kaya't ang pagkamit ng abstraction na ito sa C++ ay karaniwang ipinapatupad sa pamamagitan ng abstract/pure virtual base class, samantalang sa Java o C# ang abstraction para sa pag-loosening ng coupling ay karaniwang decoupling sa pamamagitan ng pag-abstract ng interface mula sa dependency at pag-coupling ng mas mataas na antas ng module (s) sa interface.

I-edit Para lang linawin:

"Sa ilang lugar nakikita ko rin itong tinatawag na dependency inversion"

Pagbabaligtad: Inverting dependency management mula sa isang application sa isang container (hal. Spring).

Dependency injection:

Sa halip na magsulat ng isang template ng pabrika, paano ang tungkol sa pag-inject ng isang bagay nang direkta sa klase ng kliyente. Kaya hayaan ang klase ng kliyente na sumangguni sa interface at dapat na makapag-inject tayo ng kongkretong uri sa klase ng kliyente. Sa pamamagitan nito, hindi kailangang gumamit ng bagong keyword ang klase ng kliyente at ganap itong hiwalay sa mga kongkretong klase.

Paano naman ang Inversion of Control (IoC)?

Sa tradisyunal na programming, ang daloy ng lohika ng negosyo ay tinukoy ng mga bagay na statically nakatalaga sa bawat isa. Sa pagbabaligtad ng kontrol, ang daloy ay nakasalalay sa isang object graph, na nilikha ng isang assembler instance at ginawang posible sa pamamagitan ng mga object interaction na tinukoy sa pamamagitan ng abstraction. Ang proseso ng pagbubuklod ay nakakamit sa pamamagitan ng dependency injection, bagaman ang ilan ay nangangatuwiran na ang paggamit ng isang service locator ay nagbibigay din ng inversion ng kontrol.

Ang pagbabaligtad ng kontrol bilang gabay sa disenyo ay nagsisilbi sa mga sumusunod na layunin:

  • Mayroong isang decoupling ng pagpapatupad ng isang tiyak na gawain mula sa pagpapatupad nito.
  • Ang bawat module ay maaaring tumuon sa kung ano ang nilalayon nitong gawin.
  • Ang mga module ay hindi gumagawa ng mga pagpapalagay tungkol sa kung ano ang ginagawa ng ibang mga system, ngunit umaasa sa kanilang mga kontrata.
  • Ang pagpapalit ng mga module ay hindi makakaapekto sa iba pang mga module.

Para sa pagkuha karagdagang impormasyon tingnan mo.

14 na sagot

Karaniwang sinasabi nito:

  • Ang mga abstraction ay hindi dapat nakadepende sa mga detalye. Ang mga detalye ay dapat nakadepende sa mga abstraction.

Kung bakit ito mahalaga, sa isang salita: ang pagbabago ay mapanganib, at depende sa konsepto sa halip na sa pagpapatupad, binabawasan mo ang pangangailangan para sa pagbabago sa mga site ng tawag.

Epektibo, binabawasan ng DIP ang pagsasama sa pagitan ng iba't ibang bahagi ng code. Ang ideya ay habang mayroong maraming mga paraan upang ipatupad, halimbawa, ang isang logger, ang paraan ng paggamit mo nito ay dapat na medyo matatag sa paglipas ng panahon. Kung makakapag-extract ka ng isang interface na kumakatawan sa konsepto ng pag-log, ang interface na iyon ay dapat na mas matatag sa paglipas ng panahon kaysa sa pagpapatupad nito, at ang mga site sa pagtawag ay dapat na hindi gaanong madaling kapitan sa mga pagbabagong maaari mong gawin sa pamamagitan ng pagpapanatili o pagpapalawak ng mekanismo ng pag-log na iyon.

Dahil partikular sa interface ang pagpapatupad, mayroon kang kakayahang pumili sa oras ng pagtakbo kung aling pagpapatupad ang pinakaangkop para sa iyong partikular na kapaligiran. Depende sa kaso, maaari rin itong maging kawili-wili.

Ang mga aklat na Agile Software Development, Principles, Patterns and Practices at Agile Principles, Patterns and Practices sa C# ay ang pinakamahusay na mapagkukunan para lubos na maunawaan ang mga orihinal na layunin at motibasyon sa likod ng Dependency Inversion Principle. Ang artikulong "The Dependency Reversal Principle" ay isa ring mahusay na mapagkukunan, ngunit dahil sa katotohanan na ito ay isang condensed na bersyon ng draft na kalaunan ay napunta sa mga naunang nabanggit na mga libro, nag-iiwan ito ng ilang mahahalagang talakayan tungkol sa konsepto ng pagmamay-ari ng package at mga interface na susi sa Ang prinsipyong ito ay naiiba sa mas pangkalahatang payo sa "programa para sa interface, hindi ang pagpapatupad" na makikita sa aklat na Mga Pattern ng Disenyo (Gamma, et al.).

Para sa isang maikling buod, ang prinsipyo ng dependency inversion ay pangunahing naglalayong pagbabago tradisyonal na pag-channel ng mga dependency mula sa "mas mataas na antas" na mga bahagi patungo sa "mas mababang antas" na mga bahagi, upang ang "mas mababang antas" na mga bahagi ay nakasalalay sa mga interface, pag-aari sa mga "mas mataas na antas" na mga bahagi (Tandaan: "Mas mataas na antas" na bahagi dito ay tumutukoy sa sangkap na nangangailangan ng mga panlabas na dependency/serbisyo, at hindi kinakailangan sa konseptong posisyon nito sa layered na arkitektura.) Gayunpaman, ang relasyon ay hindi bumababa kasing dami niya mga shift mula sa mga bahagi na sa teoryang hindi gaanong mahalaga hanggang sa mga bahagi na ayon sa teorya ay mas mahalaga.

Ito ay nakakamit sa pamamagitan ng pagdidisenyo ng mga bahagi na ang mga panlabas na dependency ay ipinahayag bilang isang interface kung saan ang mamimili ng bahagi ay dapat magbigay ng isang pagpapatupad. Sa madaling salita, ipinapahayag ng ilang interface kung ano ang kailangan ng component, hindi kung paano mo ginagamit ang component (halimbawa, "INeedSomething" sa halip na "IDoSomething").

Ang hindi tinutugunan ng Dependency Inversion Principle ay ang simpleng kasanayan ng pag-abstract ng mga dependency gamit ang mga interface (hal. MyService -> ). Bagama't hinihiwalay nito ang bahagi mula sa partikular na detalye ng pagpapatupad ng dependency, hindi nito binabaligtad ang relasyon sa pagitan ng consumer at ng dependency (halimbawa, ⇐ Logger.

Ang kahalagahan ng prinsipyo ng dependency inversion ay maaaring isama sa isang layunin - ang kakayahang muling gamitin ang mga bahagi ng software na umaasa sa mga panlabas na dependency para sa bahagi ng kanilang functionality (pagpaparehistro, pag-verify, atbp.)

Sa loob ng pangkalahatang layuning ito ng muling paggamit, maaari nating makilala ang dalawang subtype ng muling paggamit:

    Paggamit ng bahagi ng software sa maraming application na may mga pagpapatupad ng dependency (halimbawa, nakagawa ka ng DI container at gustong magbigay ng pag-log, ngunit ayaw mong itali ang iyong container sa isang partikular na logger, kaya dapat gamitin din ng lahat ng gumagamit ng iyong container ang pag-log library na pipiliin mo).

    Paggamit ng mga bahagi ng software sa isang umuusbong na konteksto (halimbawa, nakabuo ka ng mga bahagi ng logic ng negosyo na nananatiling pareho sa iba't ibang bersyon ng application, kung saan nagbabago ang mga detalye ng pagpapatupad).

Sa unang kaso ng muling paggamit ng mga bahagi sa maraming application, tulad ng sa isang library ng imprastraktura, ang layunin ay mabigyan ang mga consumer ng pinagbabatayan na imprastraktura nang hindi itinatali ang iyong mga consumer sa mga dependency ng sarili mong library, dahil ang pagkuha ng mga dependency mula sa mga naturang dependency ay nangangailangan ng mga consumer na kailanganin din ang parehong dependencies. Maaari itong maging problema kapag nagpasya ang mga consumer ng iyong library na gumamit ng ibang library para sa parehong mga pangangailangan sa imprastraktura (tulad ng NLog at log4net), o kung magpasya silang gumamit ng mas bagong bersyon ng kinakailangang library na hindi pabalik na tugma sa kinakailangang bersyon sa pamamagitan ng iyong library.

Sa pangalawang kaso ng muling paggamit ng mga bahagi ng lohika ng negosyo (i.e. "Mga Bahagi ng Mas Mataas na Antas"), ang layunin ay ihiwalay ang pagpapatupad ng application sa pangunahing lugar mula sa mga nagbabagong pangangailangan ng iyong mga detalye ng pagpapatupad (hal. pagbabago/pag-update ng mga patuloy na library, pagpapalitan ng mga mensahe ng library) . mga diskarte sa pag-encrypt, atbp.). Sa isip, ang pagpapalit ng mga detalye ng pagpapatupad ng application ay hindi dapat masira ang mga bahagi na sumasaklaw sa lohika ng negosyo ng application.

Tandaan. Ang ilan ay maaaring tumutol sa paglalarawan sa pangalawang kaso na ito bilang aktwal na muling paggamit, sa paniniwalang ang mga bahagi tulad ng mga bahagi ng lohika ng negosyo na ginagamit sa isang umuunlad na application ay bumubuo lamang ng isang paggamit. Ang ideya dito, gayunpaman, ay ang bawat pagbabago sa mga detalye ng pagpapatupad ng application ay kumakatawan sa isang bagong konteksto at samakatuwid ay isang ibang kaso ng paggamit, kahit na ang mga layunin sa pagtatapos ay maaaring makilala bilang paghihiwalay at portability.

Bagama't maaaring may ilang benepisyo ang pagsunod sa prinsipyo ng dependency inversion sa pangalawang kaso, dapat tandaan na ang kahalagahan nito na inilapat sa mga modernong wika tulad ng Java at C# ay nabawasan nang malaki, marahil hanggang sa punto na ito ay hindi nauugnay. Tulad ng tinalakay kanina, ang DIP ay nagsasangkot ng ganap na paghihiwalay ng mga detalye ng pagpapatupad sa magkakahiwalay na mga pakete. Sa kaso ng isang umuusbong na application, gayunpaman, ang paggamit lamang ng mga interface na tinukoy sa mga termino ng domain ng negosyo ay mapoprotektahan laban sa pangangailangang baguhin ang mas mataas na antas ng mga bahagi dahil sa pagbabago ng mga pangangailangan ng mga bahagi ng detalye ng pagpapatupad, kahit na ang mga detalye ng pagpapatupad sa huli ay nasa parehong pakete . Ang bahaging ito ng prinsipyo ay sumasalamin sa mga aspeto na nauugnay sa wika sa panahon ng pag-codification nito (halimbawa, C++), na hindi nauugnay sa mga mas bagong wika. Gayunpaman, ang kahalagahan ng Dependency Inversion Principle ay pangunahing nauugnay sa pagbuo ng mga magagamit na bahagi ng software/library.

Isang mas detalyadong talakayan sa prinsipyong ito na nauugnay sa madaling gamitin mga interface, dependency injection, at ang split interface pattern ay matatagpuan.

Kapag bumuo kami ng mga software application, maaari naming isaalang-alang ang mga mababang antas na klase - mga klase na nagpapatupad ng mga basic at pangunahing operasyon (disk access, network protocol, atbp.) at mga high-level na klase - mga klase na sumasaklaw sa kumplikadong lohika (mga daloy ng negosyo,... ) .

Ang huli ay umaasa sa mababang antas ng mga klase. Ang natural na paraan para ipatupad ang gayong mga istruktura ay ang pagsulat ng mga mababang antas ng klase at sa tuwing tayo ay mapipilitang sumulat ng mga kumplikadong mataas na antas ng mga klase. Dahil ang mga mataas na antas ng klase ay tinukoy mula sa pananaw ng iba, ito ay tila isang lohikal na paraan upang gawin ito. Ngunit hindi ito tumutugon na disenyo. Ano ang mangyayari kung kailangan nating palitan ang isang mababang antas ng klase?

Ang Dependency Inversion Principle ay nagsasaad na:

  • Ang mga high level na module ay hindi dapat nakadepende sa mga low level na module. Parehong dapat depende sa abstraction.

Ang prinsipyong ito ay naglalayong "baligtarin" ang karaniwang ideya kung saan nakapasok ang mga high-level na module software dapat nakadepende sa lower level modules. Dito, pagmamay-ari ng mga high-level na module ang abstraction (halimbawa, pagpapasya sa mga pamamaraan ng interface) na ipinapatupad ng mga lower-level na module. Kaya, ang mas mababang antas ng mga module ay nakadepende sa mas mataas na antas ng mga module.

Ang epektibong paggamit ng dependency inversion ay nagbibigay ng flexibility at stability sa kabuuan ng iyong application architecture. Papayagan nito ang iyong application na bumuo ng mas ligtas at matatag.

Tradisyunal na Layered Architecture

Ayon sa kaugalian, ang user interface ng isang layered na arkitektura ay nakadepende sa layer ng negosyo, na nakadepende naman sa layer ng pag-access ng data.

Dapat mong maunawaan ang layer, package o library. Tingnan natin kung paano napupunta ang code.

Magkakaroon kami ng library o package para sa layer ng pag-access ng data.

// DataAccessLayer.dll pampublikong klase ProductDAO ( )

// BusinessLogicLayer.dll gamit ang DataAccessLayer; pampublikong klase ProductBO ( pribadong ProductDAO productDAO; )

Layered architecture na may dependency inversion

Ang dependency inversion ay nagpapahiwatig ng sumusunod:

Ang mga high level na module ay hindi dapat nakadepende sa mga low level na module. Parehong dapat depende sa abstraction.

Ang mga abstraction ay hindi dapat nakadepende sa mga detalye. Ang mga detalye ay dapat nakadepende sa mga abstraction.

Ano ang mataas na antas at mababang antas ng mga module? Sa pag-iisip tungkol sa mga module gaya ng mga library o package, ang mga high-level na module ay yaong mga tradisyonal na may mga dependency at ang mga mababang antas kung saan sila umaasa.

Sa madaling salita, ang mataas na antas ng module ay kung saan ang aksyon ay tinatawag, at ang mababang antas kung saan ang aksyon ay isinasagawa.

Ang isang makatwirang konklusyon ay maaaring makuha mula sa prinsipyong ito: hindi dapat magkaroon ng dependence sa pagitan ng mga nodule, ngunit dapat mayroong dependence sa abstraction. Ngunit ayon sa diskarte na ginagawa namin, maaaring hindi tama ang paggamit namin ng dependency sa pamumuhunan, ngunit iyon ay isang abstraction.

Isipin na iniangkop namin ang aming code tulad nito:

Magkakaroon kami ng library o package para sa data access layer na tumutukoy sa abstraction.

// DataAccessLayer.dll pampublikong interface IPProductDAO pampublikong klase ProductDAO: IPProductDAO( )

At iba pang lohika ng negosyo sa antas ng library o package na nakadepende sa layer ng pag-access ng data.

// BusinessLogicLayer.dll gamit ang DataAccessLayer; pampublikong klase ProductBO ( pribadong IPProductDAO productDAO; )

Bagama't umaasa kami sa abstraction, ang relasyon sa pagitan ng negosyo at pag-access ng data ay nananatiling pareho.

Upang makamit ang dependency inversion, ang persistence interface ay dapat na tukuyin sa module o package kung saan naninirahan ang high-level na logic o domain, hindi sa low-level na module.

Una tukuyin kung ano ang isang layer ng domain at ang abstraction ng komunikasyon nito ay tinukoy sa pamamagitan ng pagtitiyaga.

// Domain.dll pampublikong interface IPProductRepository; gamit ang DataAccessLayer; pampublikong klase ng ProductBO ( pribadong IPProductRepository productRepository; )

Kapag ang antas ng pagtitiyaga ay nakadepende sa domain, posible na ngayong baligtarin kung ang isang dependency ay tinukoy.

// Persistence.dll public class ProductDAO: IProductRepository( )

Pagpapalalim ng prinsipyo

Mahalagang maunawaan nang mabuti ang konsepto, pagpapalalim ng layunin at mga benepisyo. Kung mananatili tayo sa mechanics at mag-aaral ng tipikal na repositoryo, hindi natin matutukoy kung saan natin mailalapat ang prinsipyo ng dependency.

Ngunit bakit natin binabaligtad ang dependency? Ano ang pangunahing layunin sa kabila ng mga tiyak na halimbawa?

Ito ay kadalasan nagbibigay-daan sa mga pinaka-matatag na bagay, na independiyente sa hindi gaanong matatag na mga bagay, na magbago nang mas madalas.

Mas madaling baguhin ang uri ng pagtitiyaga, o ang database o teknolohiya upang ma-access ang parehong database, kaysa sa domain logic o mga aksyon na idinisenyo upang makipag-usap nang may pagpupursige. Nagiging sanhi ito ng pagbabalik sa dependency dahil mas madaling baguhin ang pagtitiyaga kung nangyari ang pagbabagong iyon. Sa ganitong paraan hindi namin kailangang baguhin ang domain. Ang layer ng domain ay ang pinaka-stable sa lahat, kaya hindi ito dapat umasa sa anumang bagay.

Ngunit mayroong higit pa sa halimbawang ito ng imbakan. Maraming mga sitwasyon kung saan inilalapat ang prinsipyong ito, at mayroong mga arkitektura batay sa prinsipyong ito.

arkitektura

May mga arkitektura kung saan ang dependency inversion ang susi sa kahulugan nito. Sa lahat ng domain, ito ang pinakamahalaga, at ang mga abstraction ang tutukuyin ang protocol ng komunikasyon sa pagitan ng domain at ng iba pang package o library.

Malinis na Arkitektura

Para sa akin ang prinsipyo ng dependency inversion na inilarawan sa opisyal na artikulo

Ang problema sa C++ ay ang mga header file ay karaniwang naglalaman ng mga deklarasyon ng mga pribadong field at pamamaraan. Kaya kung ang isang mataas na antas ng C++ na module ay naglalaman ng isang header file para sa isang mababang antas ng module, ito ay depende sa aktwal pagpapatupad mga detalye ng modyul na ito. At ito ay malinaw na hindi napakahusay. Ngunit hindi ito problema sa mas modernong mga wika na karaniwang ginagamit ngayon.

Ang mga high-level na module ay likas na hindi gaanong magagamit muli kaysa sa mga low-level na module dahil ang una ay karaniwang mas partikular sa aplikasyon/konteksto kaysa sa huli. Halimbawa, ang bahagi na nagpapatupad ng screen ng UI ay ang pinakamataas na antas at napaka (ganap?) na partikular sa application. Ang pagsisikap na muling gamitin ang naturang bahagi sa isa pang application ay kontraproduktibo at maaari lamang humantong sa labis na pag-unlad.

Kaya, ang paggawa ng hiwalay na abstraction sa parehong antas ng component A na nakadepende sa component B (na hindi nakadepende sa A) ay magagawa lang kung ang component A ay talagang magiging kapaki-pakinabang para sa muling paggamit sa iba't ibang application o konteksto. Kung hindi ito ang kaso, ang DIP application ay magiging isang masamang disenyo.

Isang mas malinaw na paraan upang ipahayag ang prinsipyo ng dependency inversion:

Ang iyong mga module na sumasaklaw sa kumplikadong lohika ng negosyo ay hindi dapat direktang nakadepende sa iba pang mga module na sumasaklaw sa lohika ng negosyo. Sa halip, dapat lamang silang umasa sa mga interface sa simpleng data.

I.e., sa halip na ipatupad ang iyong Logic class tulad ng karaniwang ginagawa ng mga tao:

Class Dependency ( ... ) class Logic ( private Dependency dep; int doSomething() ( // Business logic gamit ang dep dito ) )

dapat kang gumawa ng isang bagay tulad ng:

Class Dependency ( ... ) interface Data ( ... ) class DataFromDependency implements Data ( private Dependency dep; ... ) class Logic ( int doSomething(Data data) ( // compute something with data ) )

Ang Data at DataFromDependency ay dapat na nakatira sa parehong module bilang Logic, hindi sa Dependency.

Bakit ito?

Magandang sagot at magandang halimbawa binigay na ng iba dito.

Ang punto ng dependency inversion ay upang gawing muli ang software.

Ang ideya ay na sa halip na dalawang piraso ng code na umaasa sa isa't isa, umaasa sila sa ilang abstract na interface. Maaari mong muling gamitin ang anumang bahagi nang wala ang isa pa.

Karaniwang nakakamit ito sa pamamagitan ng pag-invert ng control container (IoC) tulad ng Spring sa Java. Sa modelong ito, ang mga katangian ng mga bagay ay itinakda sa pamamagitan ng XML configuration, sa halip na mga bagay na lumalabas at hinahanap ang kanilang dependency.

Isipin ang pseudocode na ito...

Pampublikong klase MyClass ( Pampublikong Serbisyo myService = ServiceLocator.service; )

Ang MyClass ay direktang nakasalalay sa parehong klase ng Serbisyo at klase ng ServiceLocator. Ito ay kinakailangan para sa pareho kung gusto mong gamitin ito sa ibang application. Ngayon isipin mo ito...

Pampublikong klase MyClass ( pampublikong IService myService; )

Gumagamit na ngayon ang MyClass ng isang interface, ang interface ng IService. Hahayaan namin ang lalagyan ng IoC na aktwal na magtakda ng halaga ng variable na ito.

Magkaroon ng isang hotel na humihingi sa tagagawa ng pagkain para sa mga supply nito. Ibinibigay ng hotel ang pangalan ng pagkain (sabihin ang manok) sa Food Generator at ibinabalik ng Generator ang hiniling na pagkain sa hotel. Ngunit walang pakialam ang hotel sa uri ng pagkain na nakukuha at inihain nito. Kaya, ang Generator ay nagbibigay ng pagkain na may label na "Pagkain" sa hotel.

Ang pagpapatupad na ito sa JAVA

FactoryClass na may factory method. Tagabuo ng Pagkain

Pampublikong klase FoodGenerator ( Pagkain ng pagkain; pampublikong Pagkain getFood(pangalan ng String)( kung(pangalan.katumbas("isda"))( pagkain = bagong Isda(); )iba kung(pangalan.katumbas("manok"))( pagkain = bagong Manok() ibang pagkain = null;

Anotasyon ng Klase/Interface

Pampublikong abstract class Food ( //Wala sa klase ng bata ang mag-o-override sa paraang ito para matiyak ang kalidad... public void quality())( String fresh = "This is a fresh " + getName(); String tasty = "This is a masarap " + getName(); System.out.println(fresh); System.out.println(masarap); ) public abstract String getName(); )

Ang manok ay nagbebenta ng Pagkain (Concrete Class)

Ang pampublikong klase ng Chicken ay nagpapalawak ng Pagkain ( /*Lahat ng uri ng pagkain ay kailangang sariwa at malasa kaya * Hindi nila i-override ang super class na paraan na "property()"*/ public String getName())( return "Chicken"; ) )

Nagtitinda ng Pagkain ang Isda (Tiyak na Klase)

Ang pampublikong klase na Isda ay nagpapalawak ng Pagkain ( /*Lahat ng uri ng pagkain ay kailangang sariwa at malasa kaya * Hindi nila i-override ang super class na paraan na "property()"*/ public String getName())( return "Fish"; ) )

Sa wakas

Hotel

Pampublikong klase Hotel ( public static void main(String args)( //Paggamit ng Factory class.... FoodGenerator foodGenerator = new FoodGenerator(); //Isang factory method para ma-instantiate ang mga pagkain... Food food = foodGenerator.getFood( "manok");

As you can see, hindi alam ng hotel kung manok o isda. Ito ay kilala lamang na ito ay isang item ng pagkain, i.e. Ang hotel ay nakasalalay sa klase ng pagkain.

Maaari mo ring mapansin na ang klase ng Isda at Manok ay nagpapatupad ng klase ng Pagkain at hindi direktang nauugnay sa hotel. mga. ang manok at isda ay nakasalalay din sa klase ng pagkain.

Nangangahulugan ito na ang isang mataas na antas na bahagi (hotel) at isang mababang antas na bahagi (isda at manok) ay nakadepende sa isang abstraction (pagkain).

Ito ay tinatawag na dependency inversion.

Ang Dependency Inversion Principle (DIP) ay nagsasaad na

i) Ang mga high level na module ay hindi dapat nakadepende sa mga low level na module. Parehong dapat depende sa abstraction.

ii) Ang mga abstraction ay hindi dapat umasa sa mga detalye. Ang mga detalye ay dapat nakadepende sa mga abstraction.

Pampublikong interface ICustomer ( string GetCustomerNameById(int id); ) pampublikong klase na Customer: ICustomer ( //ctor public Customer()() public string GetCustomerNameById(int id) ( return "Dummy Customer Name"; ) ) public class CustomerFactory ( public static ICustomer GetCustomerData() ( return new Customer(); ) ) public class CustomerBLL ( ICustomer _customer; public CustomerBLL() ( _customer = CustomerFactory.GetCustomerData(); ) public string GetCustomerNameById(int id) ( return _customerId.GetByCustomer); ) ) pampublikong klase na Programa ( static void Main() ( CustomerBLL customerBLL = new CustomerBLL(); int customerId = 25; string customerName = customerBLL.GetCustomerNameById(customerId); Console.WriteLine(customerName); Console.ReadKey(); ) )

Tandaan. Ang isang klase ay dapat na nakadepende sa mga abstraction tulad ng isang interface o abstract na mga klase, sa halip na sa mga kongkretong detalye (interface pagpapatupad).

ibahagi

Huling na-update: 03/11/2016

Dependency Inversion Principle Ang Dependency Inversion Principle ay ginagamit upang lumikha ng maluwag na pinagsamang mga entity na madaling subukan, baguhin, at i-update. Ang prinsipyong ito ay maaaring mabalangkas tulad ng sumusunod:

Ang mga top-level na module ay hindi dapat nakadepende sa lower-level na mga module. Parehong dapat depende sa abstraction.

Ang mga abstraction ay hindi dapat nakadepende sa mga detalye. Ang mga detalye ay dapat nakadepende sa mga abstraction.

Upang maunawaan ang prinsipyo, isaalang-alang ang sumusunod na halimbawa:

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

Ang klase ng Aklat, na kumakatawan sa isang aklat, ay gumagamit ng klase ng ConsolePrinter upang mag-print. Sa kahulugang ito, nakadepende ang klase ng Aklat sa klase ng ConsolePrinter. Bukod dito, mahigpit naming tinukoy na ang pag-print ng libro ay maaari lamang gawin sa console gamit ang klase ng ConsolePrinter. Iba pang mga opsyon, halimbawa, output sa isang printer, output sa isang file, o paggamit ng ilang mga elemento ng graphical interface - lahat ng ito ay hindi kasama sa kasong ito. Ang abstraction sa pag-print ng libro ay hindi hiwalay sa mga detalye ng klase ng ConsolePrinter. Ang lahat ng ito ay isang paglabag sa prinsipyo ng dependency inversion.

Ngayon, subukan nating iayon ang ating mga klase sa prinsipyo ng dependency inversion, na naghihiwalay sa mga abstraction mula sa mababang antas ng pagpapatupad:

Interface IPrinter ( void Print(string text); ) class Book ( public string Text ( get; set; ) public IPrinter Printer ( get; set; ) public Book(IPrinter printer) ( this.Printer = printer; ) public void Print( ) ( Printer.Print(Text); ) ) class ConsolePrinter: IPrinter ( public void Print(string text) ( Console.WriteLine("Print to console"); ) ) class HtmlPrinter: IPrinter ( public void Print(string text) ( Console.WriteLine("I-print sa html");

Ang abstraction sa pag-print ng libro ay hiwalay na ngayon sa mga kongkretong pagpapatupad. Bilang resulta, parehong nakadepende ang klase ng Aklat at ang klase ng ConsolePrinter sa abstraction ng IPrinter. Bilang karagdagan, maaari na rin tayong lumikha ng mga karagdagang mababang antas na pagpapatupad ng abstraction ng IPrinter at dynamic na ilapat ang mga ito sa programa:

Book book = bagong Book(new ConsolePrinter()); book.Print(); book.Printer = bagong HtmlPrinter(); book.Print();