Ang prinsipyo ng dependency inversion ay ipinatupad gamit ang. Dependency Inversion

bahay / Nag-aaway

Sa katunayan, lahat ng mga prinsipyo SOLID ay mahigpit na magkakaugnay at ang kanilang pangunahing layunin ay tumulong na lumikha ng mataas na kalidad, nasusukat na software. Ngunit ang huling prinsipyo SOLID talagang lumalaban sa kanila. 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.. Itinatampok din ng kilalang eksperto sa pagbuo ng software, si Robert Martin, ang prinsipyo DIP at inilalahad lamang ito bilang resulta 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 gumawa ng mga bagong pagbabago, at ang pangalawa ay tumatalakay sa mana at ipinapalagay ang ligtas na paggamit ng mga nagmula na uri ng ilang uri ng base nang hindi sinisira ang tamang operasyon ng programa. Si Robert Martin ay orihinal na nagbalangkas ng prinsipyong ito tulad ng sumusunod:

isa). Ang mga module sa itaas na antas ay hindi dapat nakadepende sa mga module na 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, ito ay kinakailangan upang bumuo ng mga klase sa mga tuntunin ng abstractions, at hindi ang kanilang mga tiyak na pagpapatupad. At kung susundin mo ang mga prinsipyo OCP at LSP, kung gayon ito mismo ang ating makakamit. Samakatuwid, bumalik tayo ng kaunti sa aralin sa. Doon, bilang isang halimbawa, isinasaalang-alang namin ang klase bard, na sa simula pa lang ay naka-hardwired 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 ng gitara;

pampublikong Bard (Guitar guitar )

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, sa anumang paraan kailangan naming baguhin ang klase na ito. 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 mabuti sa lahat. Upang matugunan ng aming klase ang mga kondisyon ng prinsipyo OCP nagdagdag kami ng interface sa system instrumento, na nagpatupad 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(

voidplay();

file Guitar.java:

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

class Guitar ay nagpapatupad ng 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 klase ng Lute ay nagpapatupad ng Instrument(

@I-override

pampublikong void play()

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

Pagkatapos noon ay nagpalit na kami ng klase bard upang, kung kinakailangan, maaari nating palitan ang mga pagpapatupad ng eksaktong kailangan natin. Nagdudulot ito ng karagdagang flexibility sa system na nilikha at binabawasan ang pagkakaisa nito (malakas na dependency ng 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 ;

2 tugon

Magandang punto - ang salitang inversion ay medyo nakakagulat (dahil pagkatapos ilapat ang DIP , ang lower-level na dependency module ay tila hindi na nakadepende ngayon sa mas mataas na antas na caller module: ang tumatawag man o ang dependent ay mas maluwag na ngayong pinagsama sa pamamagitan ng karagdagang abstraction ).

Maaari mong itanong kung bakit ginagamit ko ang salitang "pagbabaligtad". Sa totoo lang, ito ay dahil mas tradisyunal na paraan ng pagbuo ng software tulad ng structured analysis at disenyo ay may posibilidad na makagawa 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 nakadepende sa mga detalye. Sa katunayan, ang isa sa mga layunin ng mga pamamaraang ito ay upang tukuyin ang isang hierarchy ng mga subroutine 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 na programa ay "baligtad" na may paggalang sa istraktura ng dependency, na kadalasang 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 kadalasang nakakamit sa pamamagitan ng abstract/pure virtual base class, samantalang sa Java. o C# ang abstraction para paluwagin ang coupling ay karaniwang i-unbind sa pamamagitan ng pag-abstract ng interface mula sa dependency at pagbubuklod sa (mga) module ng mas mataas na antas sa interface.

I-edit Para lang linawin:

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

Pagbabaligtad: Baligtarin ang pamamahala ng dependency mula sa application patungo sa lalagyan (tulad ng Spring).

Dependency Injection:

Sa halip na magsulat ng factory pattern, paano ang pag-inject ng object nang direkta sa client class. Kaya't hayaan natin ang klase ng kliyente na sumangguni sa interface at dapat nating mai-inject ang kongkretong uri sa klase ng kliyente. Sa pamamagitan nito, hindi kailangang gamitin ng klase ng kliyente ang bagong keyword at ganap na nahiwalay 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 nakadepende sa isang object graph na na-instantiate ng assembler at ginawang posible sa pamamagitan ng object interaction na tinukoy sa pamamagitan ng abstraction. Ang proseso ng bundling ay nakakamit sa pamamagitan ng dependency injection, bagama't ang ilan ay nangangatuwiran na ang paggamit ng 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 partikular na gawain mula sa pagpapatupad.
  • Ang bawat module ay maaaring tumuon sa kung ano ang nilalayon nito.
  • Ang mga module ay hindi gumagawa ng anumang 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 ibang mga module.

Tingnan para sa higit pang impormasyon.

Huling na-update: 03/11/2016

Dependency Inversion Principle(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. Kapag tinukoy tulad nito, ang klase ng Aklat ay nakasalalay sa klase ng ConsolePrinter. Bukod dito, mahigpit naming tinukoy na ang pag-print ng libro ay posible lamang 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 isang graphical na 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 sa pamamagitan ng paghihiwalay ng mga abstraction mula sa pagpapatupad ng mababang antas:

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("Print to html"); ) )

Ngayon ang abstraction sa pag-print ng libro ay hiwalay sa mga kongkretong pagpapatupad. Bilang resulta, parehong nakadepende ang klase ng Aklat at ang klase ng ConsolePrinter sa abstraction ng IPrinter. Bilang karagdagan, ngayon ay 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 Aklat(new ConsolePrinter()); book.Print(); book.Printer = bagong HtmlPrinter(); book.Print();

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 sandaling ito ay mas madali para sa akin, kahit na ang prinsipyo ng dependency inversion ay naaangkop sa anumang programming language.

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

Iba pang mga artikulo mula sa seryeng ito:

Magsimula tayo sa kahulugan ng "dependency". 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 ng klase A sa loob ng isang pamamaraan na tinatawag na someMethod() ay tahasang lumilikha ng isang object ng class B at ina-access ang pamamaraan nito someMethodOfB()

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

Katulad nito, halimbawa, ang klase B ay tahasang tumutukoy sa mga static na field at pamamaraan ng klase ng System:

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

Sa lahat ng kaso kung saan ang anumang klase (uri A) mismo ay lumilikha ng anumang klase (uri B) o tahasang nag-a-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 siya rin ang lumikha ng klase na ito sa loob ng kanyang sarili, 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 naturang code. Ngunit sa ilang mga kaso, upang ang iyong klase A ay gumana sa pangkalahatan sa kapaligiran ng iba't ibang klase, maaaring kailanganin nito ang iba pang mga pagpapatupad ng mga klase - dependencies. Yung. kakailanganin mo, halimbawa, hindi class B , ngunit isa pang klase na may parehong interface, o hindi System.out , ngunit, halimbawa, output sa isang logger (halimbawa, log4j).

Ang isang direktang dependency ay maaaring graphical na ipakita tulad nito:

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

Upang i-decouple ang class A mula sa mga partikular na dependencies, mag-apply dependency injection. Ano ang dependency injection? Sa halip na tahasang likhain ang gustong 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. Nakukuha na ngayon ng class A ang dependency nito sa pamamagitan ng constructor. Ngayon, upang makalikha ng klase A, kakailanganin mo munang lumikha ng nakadependeng klase nito. 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 , sa constructor ng class D — ang dependencies nito E at F , atbp., pagkatapos ay makakakuha ka ng code, 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();

Sa graphically, ito ay maaaring ipakita 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 "dependency inversion". Sa madaling salita, ang dependency inversion ay nakasalalay sa katotohanan 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. At nangangahulugan ito na ang iyong klase A ay maaaring madaling i-configure para magamit sa isa pang programa kaysa 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 matrabahong buuin. Samakatuwid, ang dependency injection (inversion) ay karaniwang ginagamit kasabay ng ilang library na idinisenyo upang mapadali ang gawaing ito. Halimbawa, isa sa mga library ng Google Guice. Cm. .

© 2022 skudelnica.ru -- Pag-ibig, pagtataksil, sikolohiya, diborsyo, damdamin, pag-aaway