Ang prinsipyo ng Dependency inversion ay ipinatupad gamit ang. Ang prinsipyo ng dependency inversion. Layered architecture na may dependency inversion


DISCLAIMER: Ang may-akda ng artikulong ito ay walang layunin na pahinain ang awtoridad o sa anumang paraan na saktan ang isang iginagalang na kasama bilang "Uncle" Bob Martin. Ito ay higit pa tungkol sa pag-iisip nang mas mabuti tungkol sa prinsipyo ng dependency inversion at pagsusuri sa mga halimbawang ginamit upang ilarawan ito.

Sa buong artikulo, ibibigay ko ang lahat ng kinakailangang mga panipi at mga halimbawa mula sa nabanggit na mga mapagkukunan. Ngunit upang maiwasan ang mga "spoiler" at ang iyong opinyon ay nananatiling layunin, inirerekumenda kong gumugol ng 10-15 minuto at basahin ang orihinal na paglalarawan ng prinsipyong ito sa isang artikulo o libro.

Ang prinsipyo ng dependency inversion ay ganito:

A. Ang mga top-level na module ay hindi dapat nakadepende sa lower-level na mga module. Parehong dapat depende sa abstraction.
B. Ang mga abstraction ay hindi dapat nakadepende sa mga detalye. Ang mga detalye ay dapat nakadepende sa mga abstraction.
Magsimula tayo sa unang punto.

Pagpapatong

Ang mga sibuyas ay may mga layer, ang mga cake ay may mga layer, ang mga cannibal ay may mga layer, at ang mga software system ay may mga layer din! – Shrek (c)
Ang anumang kumplikadong sistema ay hierarchical: ang bawat layer ay binuo batay sa isang napatunayan at mahusay na gumaganang layer ng isang mas mababang antas. Nagbibigay-daan ito sa iyong tumuon sa limitadong hanay ng mga konsepto sa isang pagkakataon, nang hindi iniisip kung paano ipinapatupad ang mga pinagbabatayan na layer.
Bilang resulta, nakakakuha kami ng isang bagay tulad ng sumusunod na diagram:

Figure 1 - "Naive" layering scheme

Mula sa pananaw ni Bob Martin, ang gayong pamamaraan para sa paghahati ng system sa mga layer ay walang muwang. Ang kawalan ng disenyong ito ay ang “mapanirang katangian: ang layer Patakaran depende sa mga pagbabago sa lahat ng mga layer sa daan patungo sa Kagamitan. Ang dependency na ito ay palipat.» .

Hmm... Isang napaka hindi pangkaraniwang pahayag. Kung pag-uusapan natin ang .NET platform, ang dependency ay magiging transitive lamang kung ang kasalukuyang module ay "ilalantad" ang mga module ng mas mababang antas sa pampublikong interface nito. Sa madaling salita, kung sa MekanismoLayer mayroong isang pampublikong klase na kumukuha ng isang halimbawa bilang argumento StringUtil(mula sa KagamitanLayer), pagkatapos ay ang lahat ng mga kliyente ng antas MekanismoLayer maging adik sa KagamitanLayer. Kung hindi, walang transitivity ng mga pagbabago: lahat ng mga pagbabago sa mas mababang antas ay limitado sa kasalukuyang antas at hindi lumaganap sa itaas.

Upang maunawaan ang ideya ni Bob Martin, kailangan mong tandaan na ang prinsipyo ng dependency inversion ay unang inilarawan noong 1996, at ang C++ na wika ay ginamit bilang mga halimbawa. Sa orihinal na artikulo, ang may-akda mismo ang sumulat nito ang problema ng transitivity ay umiiral lamang sa mga wika na walang malinaw na paghihiwalay ng interface ng klase mula sa pagpapatupad. Sa C++, ang problema ng transitive dependencies ay talagang may kaugnayan: kung ang isang file PolicyLayer. h kasama sa pamamagitan ng "isama" na direktiba Layer ng Mekanismo. h, na kasama naman UtilityLayer. h, pagkatapos ay para sa anumang pagbabago sa header file UtilityLayer. h(kahit sa seksyong "sarado" ng mga klase na idineklara sa file na ito) kakailanganin nating i-compile at muling i-deploy ang lahat ng mga kliyente. Gayunpaman, sa C++ ang problemang ito ay nalutas sa pamamagitan ng paggamit ng PIml idiom, na iminungkahi ni Herb Sutter at ngayon ay hindi rin masyadong nauugnay.

Ang solusyon sa problemang ito mula sa pananaw ni Bob Martin ay ito:

"Ang mas mataas na antas na layer ay nagdedeklara ng abstract na interface sa mga serbisyong kailangan nito. Ang mas mababang mga layer ay ipinatupad upang masiyahan ang mga interface na ito. Ang anumang klase na matatagpuan sa pinakamataas na antas ay nag-a-access sa layer ng layer sa tabi nito sa pamamagitan ng abstract interface. Kaya, ang mga itaas na layer ay independiyente sa mga mas mababa. Sa kabaligtaran, ang mas mababang mga layer ay nakasalalay sa abstract na interface ng mga serbisyo, inihayag sa mas mataas na antas... Kaya, sa pamamagitan ng pag-reverse ng mga dependency, lumikha kami ng isang istraktura na sa parehong oras ay mas nababaluktot, matibay at mobile.



Figure 2 - Baliktad na mga layer

Sa ilang mga paraan, ang dibisyon na ito ay may katuturan. Kaya, halimbawa, kapag ginagamit ang pattern ng tagamasid, ito ay ang naobserbahang bagay (nakikita) na tumutukoy sa interface para sa pakikipag-ugnayan sa labas ng mundo, kaya walang mga panlabas na pagbabago ang maaaring makaapekto dito.

Ngunit sa kabilang banda, pagdating partikular sa mga layer, na karaniwang kinakatawan bilang mga assemblies (o mga pakete sa mga termino ng UML), ang iminungkahing diskarte ay halos hindi matatawag na mabubuhay. Sa pamamagitan ng kahulugan, ang mas mababang antas ng mga klase ng katulong ay ginagamit sa isang dosenang iba't ibang mas mataas na antas na mga module. Layer ng Utility ay gagamitin hindi lamang sa Layer ng Mekanismo, ngunit din sa Layer ng Data Access, Transport Layer, Ilang Iba Pang Layer. Dapat ba nitong ipatupad ang mga interface na tinukoy sa lahat ng mas mataas na antas ng mga module?

Malinaw, ang solusyon na ito ay halos hindi perpekto, lalo na dahil nilulutas namin ang isang problema na hindi umiiral sa maraming mga platform, tulad ng .NET o Java.

Konsepto ng abstraction

Maraming termino ang nakatatak sa ating utak kaya hindi na natin ito binibigyang pansin. Para sa karamihan ng mga "object-oriented" na programmer, nangangahulugan ito na huminto na tayo sa pag-iisip tungkol sa marami sa mga sobrang ginagamit na termino tulad ng "abstraction", "polymorphism", "encapsulation". Bakit mo pag-isipan ang mga ito, dahil malinaw na ang lahat? ;)

Gayunpaman, upang tumpak na maunawaan ang kahulugan ng Dependency Inversion Principle at ang pangalawang bahagi ng kahulugan, kailangan nating bumalik sa isa sa mga pangunahing konseptong ito. Tingnan natin ang kahulugan ng terminong "abstraction" mula sa aklat ni Gradi Bucha:

Abstraction kinikilala ang mga mahahalagang katangian ng ilang bagay na nakikilala ito mula sa lahat ng iba pang uri ng mga bagay at, sa gayon, malinaw na tinukoy ang mga konseptong hangganan nito mula sa pananaw ng nagmamasid.

Sa madaling salita, tinutukoy ng abstraction ang nakikitang pag-uugali ng isang bagay, na sa mga termino ng programming language ay tinutukoy ng pampublikong (at protektado) na interface ng object. Kadalasan ay nagmomodelo kami ng mga abstraction gamit ang mga interface o abstract na klase, bagama't mula sa isang OOP na pananaw ay hindi ito kinakailangan.

Bumalik tayo sa kahulugan: Ang mga abstraction ay hindi dapat nakadepende sa mga detalye. Ang mga detalye ay dapat nakadepende sa mga abstraction.

Anong halimbawa ang nasa isip natin ngayon, pagkatapos nating maalala kung ano ito? abstraction? Kailan nagsisimulang umasa ang abstraction sa mga detalye? Ang isang halimbawa ng paglabag sa prinsipyong ito ay ang abstract class GZipStream, na tumatagal MemoryStream, hindi isang abstract na klase Stream:

Abstract class GZipStream ( // Ang GZipStream abstraction ay tumatanggap ng isang kongkretong stream na protektado ng GZipStream(MemoryStream memoryStream) () )

Ang isa pang halimbawa ng isang paglabag sa prinsipyong ito ay ang abstract repository class mula sa data access layer na kumukuha sa constructor PostgreSqlConnection o isang string ng koneksyon para sa SQL Server, na gumagawa ng anumang pagpapatupad ng naturang abstraction na nakatali sa isang partikular na pagpapatupad. Ngunit ito ba ang ibig sabihin ni Bob Martin? Sa paghusga sa mga halimbawang ibinigay sa artikulo o sa aklat, naiintindihan ni Bob Martin ang isang bagay na ganap na naiiba sa pamamagitan ng konsepto ng "abstraction".

PrinsipyoDIPayon kay Martin

Upang ipaliwanag ang kanyang kahulugan, ibinigay ni Bob Martin ang sumusunod na paliwanag.

Ang isang bahagyang pinasimple, ngunit napaka-epektibong interpretasyon ng prinsipyo ng DIP ay ipinahayag ng isang simpleng heuristic na panuntunan: "Kailangan mong umasa sa mga abstraction." Ito ay nagsasaad na dapat ay walang dependencies sa mga partikular na klase; lahat ng koneksyon sa programa ay dapat humantong sa isang abstract na klase o interface.

  • Dapat ay walang mga variable na nag-iimbak ng mga sanggunian sa mga partikular na klase.
  • Dapat ay walang mga klase na nagmula sa mga kongkretong klase.
  • Dapat ay walang mga pamamaraan na nag-o-override sa isang pamamaraan na ipinatupad sa isa sa mga batayang klase.

Upang ilarawan ang paglabag sa prinsipyo ng DIP sa pangkalahatan, at ang unang "paglilinaw" na punto, sa partikular, ang sumusunod na halimbawa ay ibinigay:

Pindutan ng Pampublikong klase ( pribadong Lamp lamp; pampublikong void Poll() ( kung (/* ilang kundisyon */) lamp.TurnOn(); ) )

Ngayon tandaan natin muli kung ano ito abstraction at sagutin ang tanong: may “abstraction” ba dito na nakadepende sa mga detalye? Habang iniisip mo ito o hinahanap mo ang talata na naglalaman ng sagot sa tanong na ito, gusto kong gumawa ng isang maliit na paglihis.

Ang code ay may isang kawili-wiling tampok. Sa mga bihirang pagbubukod, ang code mismo ay hindi maaaring tama o mali; Kung ito ay isang bug o isang tampok ay nakasalalay sa kung ano ang inaasahan dito. Kahit na walang pormal na espesipikasyon (na siyang pamantayan), mali lang ang code kung gumawa ito ng iba kaysa sa kinakailangan o nilalayong gawin. Ang prinsipyong ito ang sumasailalim sa programming ng kontrata, kung saan ang mga detalye (intentions) ay direktang ipinahayag sa code sa anyo ng mga preconditions, postconditions at invariants.

Nakatingin sa klase Pindutan Hindi ko masabi kung may depekto ang disenyo o hindi. Talagang masasabi kong hindi tumutugma ang pangalan ng klase sa pagpapatupad nito. Kailangang palitan ang pangalan ng klase sa LampButton o alisin sa klase Pindutan patlang lampara.

Iginiit ni Bob Martin na ang disenyong ito ay may depekto dahil “ang mataas na antas ng diskarte sa aplikasyon ay hindi hiwalay sa mababang antas ng pagpapatupad. Ang mga abstraction ay hindi nahihiwalay sa mga detalye. Sa kawalan ng gayong paghihiwalay, ang diskarte sa pinakamataas na antas ay awtomatikong nakadepende sa mas mababang antas ng mga module, at ang abstraction ay awtomatikong nakadepende sa mga detalye."

Una, Hindi ko nakikita ang "mga top-level na diskarte" at "lower-level na mga module" sa halimbawang ito: mula sa aking pananaw, mga klase Pindutan At lampara ay nasa parehong antas ng abstraction (hindi bababa sa wala akong nakikitang anumang mga argumento sa kabaligtaran). Ang katotohanan na ang klase Pindutan Ang kakayahang kontrolin ang isang tao ay hindi gumagawa sa kanila ng mas mataas na antas. Pangalawa, walang "detail-dependent abstraction" dito, mayroong "detail-dependent na pagpapatupad ng abstraction", na hindi pareho sa lahat.

Ang solusyon ni Martin ay:



Figure 3 – “Inverting dependencies”

Mas maganda ba ang solusyong ito? Tingnan natin…

Ang pangunahing bentahe ng pag-invert ng mga dependency "ayon kay Martin" ay ang pagbabaligtad ng pagmamay-ari. Sa orihinal na disenyo, kapag binabago ang klase lampara kailangang magbago ang klase Pindutan. Ngayon klase Pindutan"pagmamay-ari" ang interface ButtonServer, ngunit hindi ito maaaring magbago dahil sa mga pagbabago sa "mas mababang antas", gaya ng lampara. Kabaligtaran lang: pagbabago ng klase ButtonServer posible lamang sa ilalim ng impluwensya ng mga pagbabago sa klase ng Button, na hahantong sa mga pagbabago sa lahat ng mga inapo ng klase ButonServer!

Ang pagbabalangkas ng prinsipyo ng dependency inversion ay binubuo ng dalawang panuntunan, ang pagsunod nito ay may lubos na positibong epekto sa istraktura ng code:

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

Sa una ay hindi ito masyadong kaakit-akit, at ang mambabasa ay malamang na handa na para sa isang napaka-boring na artikulo na may isang grupo ng mga termino, kumplikadong mga pigura ng pananalita at mga halimbawa, kung saan wala pa ring malinaw. Ngunit walang kabuluhan, dahil, sa prinsipyo, napapailalim sa prinsipyo ng dependency inversion, ang lahat ay muling bumaba sa tamang paggamit at sa konteksto ng pangunahing ideya - muling paggamit ng code.

Ang isa pang konsepto na magiging nauugnay dito ay ang mahinang pagsasama ng mga uri, iyon ay, ang pagbabawas o pag-aalis ng kanilang pag-asa sa isa't isa, na, sa katunayan, ay nakamit gamit ang abstraction at polymorphism. Ito, sa katunayan, ang esensya ng prinsipyo ng dependency inversion.

Ngayon tingnan natin ang isang halimbawa na malinaw na magpapakita kung ano ang hitsura ng maluwag na pagkabit sa pagkilos.

Sabihin nating nagpasya kaming mag-order ng birthday cake. Para dito nagpunta kami sa isang kahanga-hangang panaderya sa sulok ng kalye. Nalaman namin kung maaari silang maghurno ng cake para sa amin sa ilalim ng malakas na pangalan na "Luxury", at nakatanggap ng positibong tugon, nag-order kami. Simple lang.

Ngayon, magpasya tayo kung anong mga abstraction ang kailangang isama sa code na ito. Upang gawin ito, tanungin lamang ang iyong sarili ng ilang mga katanungan:

  • Bakit cake? Maaari ka ring mag-order ng pie o cupcake
  • Bakit partikular para sa iyong kaarawan? Paano kung kasal o graduation?
  • Bakit "Luxury", paano kung mas gusto ko ang "Athill" o "Prague"?
  • Bakit ito bakery, at hindi isang pastry shop sa sentro ng lungsod o sa ibang lugar?

At ang bawat isa sa mga "paano kung" at "paano kung" ay mga punto kung saan kinakailangan ang pagpapalawak ng code, at, nang naaayon, maluwag na pagkabit at kahulugan ng mga uri sa pamamagitan ng mga abstraction.

Ngayon simulan natin ang pagbuo ng mga uri sa kanilang sarili.

Tutukuyin namin ang isang interface para sa anumang obra maestra ng confectionery na maaari naming i-order.

Interface IPastry ( Pangalan ng string ( get; set; ) )

At narito ang isang tiyak na pagpapatupad, para sa aming kaso - isang cake ng kaarawan. Tulad ng nakikita mo, tradisyonal na ang isang birthday cake ay nagsasangkot ng mga kandila)))

Class BirthdayCake: IPastry ( public int NumberOfCandles ( get; set; ) public string Name ( get; set; ) public override string ToString() ( return String.Format("(0) with (1) candle nices", Name, NumberOfCandles );))

Ngayon kung kailangan namin ng cake para sa kasal o para lang sa tsaa, o gusto namin ng mga cupcake o custard pie, mayroon kaming pangunahing interface para sa lahat.

Ang susunod na tanong ay kung paano naiiba ang mga produkto ng confectionery sa bawat isa (maliban sa pangalan, siyempre). Siyempre, may isang recipe!

At ang konsepto ng isang recipe ay may kasamang isang listahan ng mga sangkap at isang paglalarawan ng proseso ng pagluluto. Samakatuwid, magkakaroon kami ng isang hiwalay na interface para sa konsepto ng sangkap:

Interface IIingredient ( string IngredientName ( get; set;) dobleng Dami ( get; set; ) string Units ( get; set; ) )

At narito ang mga sangkap para sa aming cake: harina, mantikilya, asukal at cream:

Class Flour:IIingredient ( public string IngredientName ( get; set; ) public double Dami ( get; set; ) public string Units ( get; set; ) public string Quality ( get; set; ) ) class Butter: IIingredient ( public string IngredientName ( get; set; ) public double Dami ( get; set; ) public string Units ( get; set; ) ) class Sugar: IIingredient ( public string IngredientName ( get; set; ) public double Dami ( get; set; ) public string Units ( get; set; ) public string Uri ( get; set; ) ) class Creme: IIingredient ( public string IngredientName ( get; set; ) public double Dami ( get; set; ) public string Units ( get; set; ) public dobleng Taba (kumuha; itakda; ) )

Ang listahan ng mga sangkap ay maaaring magkakaiba sa iba't ibang mga recipe at iba't ibang mga produkto ng confectionery, ngunit para sa aming recipe ang listahang ito ay sapat.

Ngayon ay oras na upang lumipat sa konsepto ng isang recipe. Anuman ang aming niluto, alam namin sa anumang kaso kung ano ang tawag dito, kung ano ito, kung anong mga sangkap ang kasama sa ulam at kung paano lutuin ito.

Interface IRecipe ( Uri ng PastryType ( get; set; ) Pangalan ng string ( get; set;) IList Mga sangkap ( get;set;) string Paglalarawan ( get;set;) )

Sa partikular, ganito ang hitsura ng klase na kumakatawan sa recipe ng birthday cake:

Class BirthdayCakeRecipe: IRecipe ( public Type PastryType ( get; set; ) public string Pangalan ( get; set;) public IList Mga sangkap ( get; set; ) public string Paglalarawan ( get; set; ) public BirthdayCakeReipe() ( Ingredients = new List (); } }

Ngayon ay lumipat tayo sa aming napakagandang panaderya sa sulok ng kalye.

Siyempre, maaari kaming mag-apply sa maraming iba pang mga panaderya, kaya tutukuyin din namin ang isang pangunahing interface para doon. Ano ang pinakamahalagang bagay para sa isang panaderya? Kakayahang maghurno ng mga produkto.

Interface IBakery ( IPastry Bake(IRecipe recipe); )

At narito ang klase na kumakatawan sa aming panaderya:

Class NiceBakeryOnTheCornerOFMyStreet ( Dictionary menu = bagong Diksyunaryo (); public void AddToMenu(IRecipe recipe) ( if (!menu.ContainsKey(recipe.Name)) ( menu.Add(recipe.Name, recipe); ) else ( Console.WriteLine("Nasa menu na ito"); ) ) pampublikong IRecipe FindInMenu(pangalan ng string) ( kung (menu.ContainsKey(pangalan)) ( return menu; ) Console.WriteLine("Paumanhin...sa kasalukuyan ay wala kaming " + pangalan); return null; ) pampublikong IPastry Bake (Irecipe recipe) ( if (recipe != null) ( IPastriyang pastry = Activator.CreateInstance(recipe.PastryType) bilang IPastry; kung (pastry != null) ( pastry.Name = recipe.Name; ibalik ang pastry bilang IPastry; ) ) ibalik ang null;))

Ang natitira na lang ay subukan ang code:

Class Program ( static void Main() ( //paglikha ng inctance ng bakery class var bakery = new NiceBakeryOnTheCornerOFMyStreet(); //paghahanda ng mga sangkap para sa recipe var flour = new Flour() ( IngredientName = "Flour", Dami = 1.5 , Units = "kg" ); var butter = new Butter() ( IngredientName = "Butter", Dami = 0.5, Units = "kg" ); var sugar = new Sugar() ( IngredientName = "Sugar", Dami = 0.7 , Units = "kg" ); var creme = new Creme() ( IngredientName = "Creme", Dami = 1.0, Units = "liters" ); //at ito ang mismong recipe var weddingCakeRecipe = new BirthdayCakeRecipe() ( PastryType = typeof(BirthdayCake), Name = "Birthday Cake Luxury", Description = "paglalarawan kung paano gumawa ng magandang birthday cake" ); weddingCakeRecipe.Ingredients.Add(harina); weddingCakeRecipe.Ingredients.Add(butter); weddingCakeRecipe.Ingredients .Add(sugar); weddingCakeRecipe.Ingredients.Add(creme); //adding our cake recipe to the bakery's menu bakery.AddToMenu(weddingCakeRecipe); //now let"s order it!! BirthdayCake cake = bakery.Bake(bakery.FindInMenu("Birthday Cake Luxury")) as BirthdayCake; //adding some candles ;) cake.NumberOfCandles = 10; //and here we are !!! Console.WriteLine(cake); ) )

Ngayon tingnan natin muli ang buong code at suriin ito. Ang code ay medyo simple, ang mga uri, ang kanilang mga abstraction, data at pag-andar ay malinaw na inilarawan, ang code ay nagbibigay-daan para sa extension at muling paggamit. Ang bawat segment ay maaaring palitan nang walang sakit ng isa pang tumutugma sa uri ng base, at hindi nito masisira ang natitirang bahagi ng code.

Maaari kang walang katapusang magdagdag ng mga uri ng sangkap, mga recipe para sa iba't ibang uri ng mga produktong confectionery, lumikha ng iba pang mga klase na naglalarawan sa mga panaderya, mga tindahan ng kendi at iba pang katulad na mga establisyimento.

Hindi masamang resulta. At lahat salamat sa katotohanan na sinubukan naming gawing minimal na nauugnay ang mga klase sa isa't isa.

Ngayon isipin natin ang mga kahihinatnan ng paglabag sa prinsipyo ng dependency inversion:

  1. rigidity (napakahirap gumawa ng anumang mga pagbabago sa system, dahil ang bawat pagbabago ay nakakaapekto sa maraming iba't ibang bahagi nito).
  2. fragility (kapag may anumang mga pagbabago na ginawa sa isang bahagi ng system, ang ibang bahagi nito ay nagiging vulnerable, at kung minsan hindi ito masyadong halata sa unang tingin).
  3. immobility (maaari mong kalimutan ang tungkol sa muling paggamit ng code sa ibang mga system, dahil ang mga module ay malakas na magkakaugnay).

Buweno, ngayon ay gumuhit ng iyong sariling mga konklusyon tungkol sa kung gaano kapaki-pakinabang ang prinsipyo ng dependency inversion sa code. Sa tingin ko ay halata ang sagot.

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 bahaging "mas mataas na antas." (Tandaan: Ang bahaging "mas mataas na antas" 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.

Ang isang mas detalyadong talakayan ng prinsipyong ito na nauugnay sa simpleng paggamit ng 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.

Nilalayon ng prinsipyong ito na "baligtarin" ang karaniwang ideya na ang mga high-level na module sa software ay dapat nakadepende sa lower-level na mga module. 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?

Ang magagandang sagot at magandang halimbawa ay naibigay 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(); )iba pang pagkain = null; ibalik ang pagkain; ) )

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); ) pampublikong 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 klaseng 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"); food.quality(); ) )

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 modules. 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, ang klase ng Aklat at ang klase ng ConsolePrinter ay nakadepende 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();