Կախվածության հակադարձման սկզբունքն իրականացվում է օգտագործելով. Կախվածության ինվերսիայի սկզբունքը. Շերտավոր ճարտարապետություն՝ կախվածության հակադարձմամբ


ՀՐԱԺԵՇՏՈՒՄԱյս հոդվածի հեղինակը նպատակ չունի խարխլել հեղինակությունը կամ որևէ կերպ վիրավորել այնպիսի հարգված ընկերոջը, ինչպիսին «քեռի» Բոբ Մարտինն է: Դա ավելի շատ կախվածության ինվերսիայի սկզբունքի մասին ավելի ուշադիր մտածելն է և այն նկարագրելու համար օգտագործված օրինակները վերլուծելը:

Հոդվածի ողջ ընթացքում կներկայացնեմ բոլոր անհրաժեշտ մեջբերումներն ու օրինակները վերը նշված աղբյուրներից։ Բայց որպեսզի «սփոյլերներից» խուսափեք, և ձեր կարծիքը մնա օբյեկտիվ, խորհուրդ կտամ 10-15 րոպե հատկացնել և կարդալ այս սկզբունքի բնօրինակ նկարագրությունը հոդվածում կամ գրքում։

Կախվածության ինվերսիայի սկզբունքը հետևյալն է.

A. Վերին մակարդակի մոդուլները չպետք է կախված լինեն ավելի ցածր մակարդակի մոդուլներից: Երկուսն էլ պետք է կախված լինեն վերացականությունից:
Բ. Աբստրակցիաները չպետք է կախված լինեն մանրամասներից: Մանրամասները պետք է կախված լինեն աբստրակցիաներից:
Սկսենք առաջին կետից.

Շերտավորում

Սոխն ունի շերտեր, տորթերը՝ շերտեր, մարդակերները՝ շերտեր, և ծրագրային համակարգերը՝ նույնպես: - Շրեկ (գ)
Ցանկացած բարդ համակարգ հիերարխիկ է. յուրաքանչյուր շերտ կառուցված է ավելի ցածր մակարդակի ապացուցված և լավ գործող շերտի հիման վրա: Սա թույլ է տալիս միաժամանակ կենտրոնանալ սահմանափակ հասկացությունների վրա՝ առանց մտածելու, թե ինչպես են իրականացվում հիմքում ընկած շերտերը:
Արդյունքում մենք ստանում ենք հետևյալ գծապատկերի նման մի բան.

Նկար 1 – «Միամիտ» շերտավորման սխեմա

Բոբ Մարտինի տեսանկյունից համակարգը շերտերի բաժանելու նման սխեման է միամիտ. Այս դիզայնի թերությունը «նենգ հատկանիշն է՝ շերտը Քաղաքականությունկախված է ճանապարհին բոլոր շերտերի փոփոխություններից Կոմունալ. Այս կախվածությունը անցողիկ է.» .

Հմմ... Շատ անսովոր հայտարարություն. Եթե ​​խոսենք .NET հարթակի մասին, ապա կախվածությունը կլինի անցումային միայն այն դեպքում, եթե ներկայիս մոդուլը «բացահայտի» ավելի ցածր մակարդակների մոդուլներ իր հանրային ինտերֆեյսում: Այլ կերպ ասած, եթե ներս ՄեխանիզմՇերտկա հանրային դաս, որը որպես փաստարկ ընդունում է օրինակը StringUtil(ից ԿոմունալՇերտ), ապա մակարդակի բոլոր հաճախորդները ՄեխանիզմՇերտդառնալ կախվածություն ԿոմունալՇերտ. Հակառակ դեպքում փոփոխությունների անցողիկություն չկա. ցածր մակարդակի բոլոր փոփոխությունները սահմանափակվում են ընթացիկ մակարդակով և չեն տարածվում վերևում.

Բոբ Մարտինի գաղափարը հասկանալու համար պետք է հիշել, որ կախվածության ինվերսիայի սկզբունքը առաջին անգամ նկարագրվել է 1996 թվականին, և որպես օրինակ օգտագործվել է C++ լեզուն։ Բնօրինակ հոդվածում հեղինակն ինքը գրում է, որ Տրանզիտիվության խնդիրը գոյություն ունի միայն այն լեզուներում, որոնք չունեն դասի միջերեսի հստակ տարանջատում իրականացումից. C++-ում անցումային կախվածությունների խնդիրն իսկապես արդիական է՝ եթե ֆայլ PolicyLayer. հներառում է «ներառել» հրահանգի միջոցով Մեխանիզմի շերտ. հ, որն իր հերթին ներառում է UtilityLayer. հ, ապա վերնագրի ֆայլի ցանկացած փոփոխության համար UtilityLayer. հ(նույնիսկ այս ֆայլում հայտարարված դասերի «փակ» բաժնում) մենք ստիպված կլինենք վերակազմավորել և վերաբաշխել բոլոր հաճախորդները: Այնուամենայնիվ, C++-ում այս խնդիրը լուծվում է Հերբ Սաթերի կողմից առաջարկված և այժմ ոչ այնքան ակտուալ PIml բառի միջոցով:

Բոբ Մարտինի տեսանկյունից այս խնդրի լուծումը հետևյալն է.

«Ավելի բարձր մակարդակի շերտը հայտարարում է վերացական ինտերֆեյս իրեն անհրաժեշտ ծառայությունների համար: Ստորին շերտերն այնուհետև ներդրվում են այս միջերեսները բավարարելու համար: Ցանկացած դաս, որը գտնվում է վերին մակարդակում, աբստրակտ ինտերֆեյսի միջոցով մուտք է գործում իր կողքի շերտի շերտը: Այսպիսով, վերին շերտերը անկախ են ստորիններից: Ընդհակառակը, ստորին շերտերը կախված են վերացական ծառայությունների ինտերֆեյսից, հայտարարեցավելի բարձր մակարդակով... Այսպիսով, փոխելով կախվածությունները, մենք ստեղծեցինք մի կառույց, որը միևնույն ժամանակ ավելի ճկուն է, դիմացկուն և շարժական.



Նկար 2 – Շրջված շերտեր

Որոշ առումներով այս բաժանումը իմաստ ունի: Այսպիսով, օրինակ, դիտորդի օրինաչափությունն օգտագործելիս դա դիտարկվող օբյեկտն է (դիտելի), որը սահմանում է արտաքին աշխարհի հետ փոխազդեցության միջերեսը, ուստի ոչ մի արտաքին փոփոխություն չի կարող ազդել դրա վրա:

Բայց մյուս կողմից, երբ խոսքը վերաբերում է հատկապես շերտերին, որոնք սովորաբար ներկայացված են որպես հավաքներ (կամ UML տերմիններով փաթեթներ), առաջարկվող մոտեցումը դժվար թե կարելի է կենսունակ անվանել: Ըստ սահմանման, ավելի ցածր մակարդակի օգնական դասերը օգտագործվում են տասնյակ տարբեր բարձր մակարդակի մոդուլներում: Կոմունալ շերտկօգտագործվի ոչ միայն Մեխանիզմի շերտ, այլեւ ներս Տվյալների հասանելիության շերտ, Տրանսպորտային շերտ, Որոշ այլ շերտ. Արդյո՞ք այն պետք է իրականացնի բոլոր բարձր մակարդակի մոդուլներում սահմանված միջերեսները:

Ակնհայտ է, որ այս լուծումը հազիվ թե իդեալական լինի, մանավանդ, որ մենք լուծում ենք մի խնդիր, որը գոյություն չունի բազմաթիվ հարթակներում, ինչպիսիք են .NET կամ Java:

Աբստրակցիայի հայեցակարգ

Շատ տերմիններ այնքան են արմատավորվում մեր ուղեղում, որ մենք դադարում ենք ուշադրություն դարձնել դրանց վրա: «Օբյեկտ-կողմնորոշված» ծրագրավորողների մեծամասնության համար դա նշանակում է, որ մենք դադարում ենք մտածել չափազանց օգտագործված տերմիններից շատերի մասին, ինչպիսիք են «աբստրակցիա», «պոլիմորֆիզմ», «կապսուլյացիա»: Ինչու՞ մտածել դրանց մասին, քանի որ ամեն ինչ արդեն պարզ է։ ;)

Այնուամենայնիվ, կախվածության ինվերսիոն սկզբունքի և սահմանման երկրորդ մասի իմաստը ճշգրիտ հասկանալու համար մենք պետք է վերադառնանք այս հիմնարար հասկացություններից մեկին: Դիտարկենք «աբստրակցիա» տերմինի սահմանումը Գրադի Բուխայի գրքից.

Աբստրակցիա բացահայտում է որոշ օբյեկտի էական բնութագրերը, որոնք տարբերում են այն բոլոր այլ տեսակի օբյեկտներից և, հետևաբար, հստակ սահմանում են դրա հայեցակարգային սահմանները դիտորդի տեսանկյունից:

Այլ կերպ ասած, աբստրակցիան սահմանում է օբյեկտի տեսանելի վարքագիծը, որը ծրագրավորման լեզուների տերմիններում որոշվում է օբյեկտի հանրային (և պաշտպանված) միջերեսով: Շատ հաճախ մենք մոդելավորում ենք աբստրակցիաներ՝ օգտագործելով միջերեսներ կամ վերացական դասեր, թեև OOP-ի տեսանկյունից դա անհրաժեշտ չէ:

Եկեք վերադառնանք սահմանմանը. Աբստրակցիաները չպետք է կախված լինեն մանրամասներից: Մանրամասները պետք է կախված լինեն աբստրակցիաներից:

Ի՞նչ օրինակ է գալիս մտքում հիմա, երբ մենք հիշում ենք, թե որն է դա: աբստրակցիա? Ե՞րբ է աբստրակցիան սկսում կախված լինել մանրամասներից: Այս սկզբունքի խախտման օրինակ է աբստրակտ դասը GZipStream, որը տանում է MemoryStream, ոչ վերացական դաս Հոսք:

Աբստրակտ դաս GZipStream ( // GZipStream աբստրակցիան ընդունում է կոնկրետ հոսքի պաշտպանված GZipStream (MemoryStream memoryStream) () )

Այս սկզբունքի խախտման մեկ այլ օրինակ կլինի վերացական պահեստի դասը տվյալների հասանելիության շերտից, որը վերցնում է կոնստրուկտորը: PostgreSqlConnectionկամ միացման տող SQL Server-ի համար, որը նման աբստրակցիայի ցանկացած իրականացում կապում է կոնկրետ իրականացման հետ: Բայց արդյո՞ք սա նկատի ունի Բոբ Մարտինը: Դատելով հոդվածում կամ գրքում բերված օրինակներից՝ Բոբ Մարտինը բոլորովին այլ բան է հասկանում «աբստրակցիա» հասկացությունից։

ՍկզբունքDIPըստ Մարտինի

Իր սահմանումը բացատրելու համար Բոբ Մարտինը տալիս է հետևյալ բացատրությունը.

DIP սկզբունքի մի փոքր պարզեցված, բայց դեռ շատ արդյունավետ մեկնաբանությունը արտահայտվում է պարզ էվրիստիկ կանոնով. «Դուք պետք է կախված լինեք աբստրակցիաներից»: Այն նշում է, որ չպետք է կախվածություն լինի կոնկրետ դասերից. Ծրագրի բոլոր կապերը պետք է հանգեցնեն վերացական դասի կամ ինտերֆեյսի:

  • Չպետք է լինեն փոփոխականներ, որոնք պահում են հղումները կոնկրետ դասերի:
  • Չպետք է լինեն դասեր, որոնք բխում են կոնկրետ դասերից:
  • Չպետք է լինի այնպիսի մեթոդներ, որոնք հաղթահարեն բազային դասերից մեկում ներդրված մեթոդը:

DIP սկզբունքի խախտումն ընդհանրապես, և առաջին «պարզաբանող» կետը, մասնավորապես, բերված է հետևյալ օրինակը.

Հանրային դասի կոճակ ( մասնավոր լամպի լամպ; հանրային void Poll() (եթե (/* որոշ պայման */) լամպ. TurnOn(); ) )

Հիմա եւս մեկ անգամ հիշենք, թե դա ինչ է աբստրակցիաև պատասխանեք հարցին՝ կա՞ այստեղ «աբստրակցիա», որը կախված է մանրամասներից։ Մինչ դուք մտածում եք այս մասին կամ փնտրում եք այս հարցի պատասխանը պարունակող պարբերությունը, ես ուզում եմ մի փոքր շեղում անել։

Կոդն ունի մեկ հետաքրքիր առանձնահատկություն. Հազվագյուտ բացառություններով, կոդը ինքնին չի կարող լինել ճիշտ կամ սխալ. Անկախ նրանից, թե դա վրիպակ է, թե հատկանիշ, կախված է նրանից, թե ինչ է սպասվում դրանից: Նույնիսկ եթե չկա պաշտոնական հստակեցում (որը նորմ է), ծածկագիրը սխալ է միայն այն դեպքում, եթե այն անում է այլ բան, քան պահանջվում է կամ նախատեսված է անել: Հենց այս սկզբունքն է ընկած պայմանագրային ծրագրավորման հիմքում, որտեղ հստակեցումը (մտադրությունները) արտահայտվում են ուղղակիորեն կոդի մեջ՝ նախապայմանների, հետպայմանների և ինվարիանտների տեսքով:

Նայելով դասարանին ԿոճակՉեմ կարող ասել՝ դիզայնը թերի է, թե ոչ։ Միանշանակ կարող եմ ասել, որ դասի անվանումը չի համապատասխանում դրա իրականացմանը։ Դասարանը պետք է վերանվանվի Լամպի կոճակկամ հեռացնել դասից Կոճակդաշտ Լամպ.

Բոբ Մարտինը պնդում է, որ այս դիզայնը թերի է, քանի որ «բարձր մակարդակի կիրառման ռազմավարությունը չի բաժանվում ցածր մակարդակի իրականացումից: Աբստրակցիաները առանձնացված չեն մանրուքներից։ Նման տարանջատման բացակայության դեպքում վերին մակարդակի ռազմավարությունը ավտոմատ կերպով կախված է ստորին մակարդակի մոդուլներից, իսկ աբստրակցիան ավտոմատ կերպով կախված է մանրամասներից»:

Նախ, Այս օրինակում ես չեմ տեսնում «վերին մակարդակի ռազմավարություններ» և «ցածր մակարդակի մոդուլներ»:Իմ տեսանկյունից՝ դասեր ԿոճակԵվ Լամպնույն աբստրակցիայի մակարդակում են (համենայն դեպս ես հակառակի փաստարկներ չեմ տեսնում): Այն փաստը, որ դաս ԿոճակԻնչ-որ մեկին կառավարել կարողանալը նրան չի դարձնում ավելի բարձր մակարդակ: Երկրորդ՝ այստեղ «դետալից կախված վերացականություն» չկա, կա «վերացականի դետալից կախված իրականացում», որն ամենևին էլ նույնը չէ։

Մարտինի լուծումը հետևյալն է.



Գծապատկեր 3 – «Կախվածությունների շրջում»

Արդյո՞ք այս լուծումն ավելի լավն է: Եկեք նայենք…

«Ըստ Մարտինի» կախվածությունների հակադարձման հիմնական առավելությունը սեփականության շրջումն է։ Բնօրինակ դիզայնով, դասարանը փոխելիս Լամպդասարանը պետք է փոխվեր Կոճակ. Հիմա դաս Կոճակ«տիրում է» ինտերֆեյսին ButtonServer, բայց այն չի կարող փոխվել «ստորին մակարդակներում» փոփոխությունների պատճառով, ինչպիսիք են Լամպ. Ճիշտ հակառակն է՝ դասակարգային փոփոխություն ButtonServerհնարավոր է միայն Button դասի փոփոխությունների ազդեցության տակ, ինչը կհանգեցնի դասի բոլոր հետնորդների փոփոխությունների ButonServer!

Կախվածության ինվերսիայի սկզբունքի ձևակերպումը բաղկացած է երկու կանոնից, որոնց պահպանումը չափազանց դրական է ազդում կոդի կառուցվածքի վրա.

  • Վերին մակարդակի մոդուլները չպետք է կախված լինեն ավելի ցածր մակարդակի մոդուլներից: Երկուսն էլ պետք է կախված լինեն վերացականությունից:
  • աբստրակցիաները չպետք է կախված լինեն մանրամասներից: Մանրամասները պետք է կախված լինեն աբստրակցիաներից:

Սկզբում դա այնքան էլ գրավիչ չի թվում, և ընթերցողը, հավանաբար, արդեն պատրաստ է մի շատ ձանձրալի հոդվածի մի փունջ տերմիններով, բարդ խոսքի պատկերներով և օրինակներով, որոնցից, այնուամենայնիվ, ոչինչ պարզ չէ: Բայց իզուր, քանի որ, սկզբունքորեն, կախվածության ինվերսիայի սկզբունքին ենթարկվելով, ամեն ինչ նորից հանգում է ճիշտ օգտագործմանը և հիմնական գաղափարի համատեքստում՝ կոդի վերաօգտագործմանը։

Մյուս հայեցակարգը, որն այստեղ տեղին կլինի, տիպերի թույլ զուգավորումն է, այսինքն՝ նվազեցնելով կամ վերացնելով նրանց կախվածությունը միմյանցից, ինչը, ըստ էության, ձեռք է բերվում աբստրակցիայի և պոլիմորֆիզմի միջոցով։ Սա, ըստ էության, կախվածության ինվերսիայի սկզբունքի էությունն է։

Հիմա եկեք նայենք մի օրինակի, որը հստակ ցույց կտա, թե ինչ տեսք ունի չամրացված միացումը գործողության մեջ:

Ասենք՝ որոշել ենք ծննդյան տորթ պատվիրել։ Սրա համար գնացինք փողոցի անկյունում գտնվող հրաշալի հացի փուռ։ Մենք պարզեցինք, թե կարո՞ղ են մեզ համար «Շքեղություն» բարձրաձայն տորթ թխել, և ստանալով դրական պատասխան՝ պատվիրեցինք։ Դա պարզ է.

Հիմա եկեք որոշենք, թե ինչ աբստրակցիաներ պետք է ներառվեն այս օրենսգրքում: Դա անելու համար պարզապես ինքներդ ձեզ մի քանի հարց տվեք.

  • Ինչու՞ տորթ: Կարող եք նաև պատվիրել կարկանդակ կամ կեքս
  • Ինչու՞ հենց քո ծննդյան օրը: Իսկ եթե դա լինի հարսանիք կամ ավարտական:
  • Ինչու՞ «Շքեղություն», իսկ եթե ինձ ավելի շատ դուր է գալիս «Մրջնանոցը» կամ «Պրահանը»:
  • Ինչո՞ւ հենց այս հացատունը, այլ ոչ թե քաղաքի կենտրոնում կամ մեկ այլ վայրում գտնվող հրուշակեղենի խանութ։

Եվ այս «ինչ, եթե» և «ինչ, եթե» յուրաքանչյուրը կետեր են, որտեղ պահանջվում է ծածկագրի ընդարձակելիություն, և, համապատասխանաբար, աբստրակցիաների միջոցով տեսակների ազատ միացում և սահմանում:

Հիմա եկեք սկսենք կառուցել տիպերն իրենք:

Մենք կսահմանենք ինտերֆեյս ցանկացած հրուշակեղենի գլուխգործոցի համար, որը մենք կարող ենք պատվիրել:

Ինտերֆեյսի IPastry (տողի անունը (ստացեք; սահմանել;))

Եվ ահա կոնկրետ իրականացում, մեր դեպքում՝ ծննդյան տորթ։ Ինչպես տեսնում եք, ավանդաբար ծննդյան տորթը ներառում է մոմեր)))

Class Birthday Cake. IPastry ( public int NumberOfCandles ( get; set; ) public string Անուն ( get; set; ) public override string ToString() ( return String.Format ("(0) with (1) candle nices", Name, NumberOfCandles ) ;))

Այժմ, եթե մեզ տորթ է պետք հարսանիքի կամ պարզապես թեյի համար, կամ ուզում ենք կարկանդակներ կամ կրեմով կարկանդակներ, մենք բոլորի համար ունենք հիմնական ինտերֆեյս:

Հաջորդ հարցն այն է, թե ինչով են տարբերվում հրուշակեղենի արտադրատեսակները (իհարկե, բացի անվանումից)։ Իհարկե, բաղադրատոմսով!

Իսկ բաղադրատոմսի գաղափարը ներառում է բաղադրիչների ցանկը և պատրաստման գործընթացի նկարագրությունը: Հետևաբար, մենք կունենանք առանձին ինտերֆեյս բաղադրիչի հայեցակարգի համար.

Interface IIingredient ( string IngredientName ( get;set;) կրկնակի Քանակ ( ստանալ; set; ) string Units ( get; set; ) )

Իսկ ահա մեր տորթի բաղադրիչները՝ ալյուր, կարագ, շաքարավազ և սերուցք.

Class Flour:IIingredient ( public string IngredientName ( get; set; ) public double Quantity ( get; set; ) public string Units ( get; set; ) public string Quality ( get; set; ) ) դաս Կարագ: IIingredient ( public string IngredientName ( get; set; ) public double Քանակ ( get; set; ) public string Units ( get; set; ) ) class Sugar: IIingredient ( public string IngredientName ( get; set; ) public double Quantity ( get; set; ) public string Միավորներ ( get; set; ) public string Kind ( get; set; ) ) class Creme: IIingredient ( public string IngredientName ( get; set; ) public double Քանակ ( get; set; ) public string Units ( get; set; ) public կրկնակի ճարպ (ստանալ; սահմանել;))

Բաղադրիչների ցանկը կարող է տարբերվել տարբեր բաղադրատոմսերի և հրուշակեղենի տարբեր արտադրանքներում, բայց մեր բաղադրատոմսի համար այս ցանկը բավարար է:

Այժմ ժամանակն է անցնել բաղադրատոմսի հայեցակարգին: Ինչ էլ որ եփենք, ամեն դեպքում գիտենք, թե դա ինչպես է կոչվում, ինչ է, ինչ բաղադրիչներ են ներառված ուտեստի մեջ և ինչպես պատրաստել այն։

Ինտերֆեյսի IRRecipe ( Type PastryType ( get; set; ) string Անուն ( get; set;) IList Բաղադրիչներ ( get;set;) string Description (get;set;) )

Մասնավորապես, ծննդյան տորթի բաղադրատոմսը ներկայացնող դասարանն ունի հետևյալ տեսքը.

Ծննդյան տորթ Բաղադրատոմս. Ingredients ( get; set; ) public string Նկարագրություն ( get; set; ) public BirthdayCakeReipe() ( Ingredients = new List (); } }

Հիմա եկեք անցնենք փողոցի անկյունում գտնվող մեր հրաշալի հացատանը։

Իհարկե, մենք կարող ենք դիմել շատ այլ հացթուխների, այնպես որ մենք դրա համար նույնպես կսահմանենք հիմնական ինտերֆեյս: Ո՞րն է ամենակարևորը հացաբուլկեղենի համար: Ապրանքներ թխելու ունակություն:

Ինտերֆեյս IBBakery (IPastry Bake (IRecipe բաղադրատոմսը);

Եվ ահա մեր հացատունը ներկայացնող դասարանը.

Դաս NiceBakeryOnTheCornerOFMyStreet ( Բառարան մենյու = նոր Բառարան (); public void AddToMenu(IRecipe բաղադրատոմսը) ( if (!menu.ContainsKey(recipe.Name)) ( menu.Add(recipe.Name, բաղադրատոմս); ) else ( Console.WriteLine(«Այն արդեն մենյուում է»);) ) public IRecipe FindInMenu(string name) ( if (menu.ContainsKey(name)) (return menu;) Console.WriteLine(«Ներողություն...ներկայումս չունենք» + անուն); վերադարձնել null; ) public IPastry Bake (IRrecipe բաղադրատոմսը) ( if (բաղադրատոմս != null) ( IPastry խմորեղեն = Activator.CreateInstance(recipe.PastryType) որպես IPastry; if ( pastry != null) ( pastry.Name = recipe.Name; վերադարձնել խմորեղենը որպես IPastry; ) ) վերադարձնել զրոյական;))

Մնում է միայն փորձարկել կոդը.

Դասի ծրագիր ( static void Main() ( //հացաբուլկեղենի դասի ինկտիվայի ստեղծում var bakery = new NiceBakeryOnTheCornerOFMyStreet(); //պատրաստում բաղադրիչներ բաղադրատոմսի համար var flour = new Flour() ( IngredientName = «Ալյուր», Քանակ = 1.5 , Միավորներ = «կգ»); var կարագ = նոր Կարագ () (Բաղադրիչի անունը = «Կարագ», Քանակ = 0.5, Միավորներ = «կգ»); var շաքար = նոր Շաքար () (Բաղադրիչի անունը = «Շաքար», Քանակ = 0.7 , Units = «kg»); var creme = new Creme() ( IngredientName = «Creme», Քանակ = 1.0, Units = «լիտր»); //և սա ինքնին բաղադրատոմսն է var weddingCakeRecipe = new BirthdayCakeRecipe() ( PastryType = typeof(BirthdayCake), Name = "Birthday Cake Luxury", Նկարագրություն = "Նկարագրություն, թե ինչպես պատրաստել ծննդյան գեղեցիկ տորթ"); weddingCakeRecipe.Ingredients.Add(flour); weddingCakeRecipe.Ingredients.Add(karag); weddingCakeRecipe.Ingredient .Ավելացնել (շաքար); հարսանեկան տորթ բաղադրատոմս.Բաղադրիչներ.Ավելացնել(կրեմ); //հիմա եկեք պատվիրենք!! BirthdayCake cake = bakery.Bake(bakery.FindInMenu("Birthday Cake Luxury")) որպես BirthdayCake; //ավելացնելով մի քանի մոմեր;) cake.NumberOfCandles = 10; //և ահա մենք ենք !!! Console.WriteLine(տորթ); ))

Հիմա նորից նայենք ամբողջ կոդը և գնահատենք այն։ Կոդը բավականին պարզ է, տեսակները, դրանց աբստրակցիաները, տվյալները և ֆունկցիոնալությունը հստակ ուրվագծված են, ծածկագիրը թույլ է տալիս ընդլայնել և վերօգտագործել: Յուրաքանչյուր հատված կարող է առանց ցավի փոխարինվել մեկով, որը համապատասխանում է հիմնական տեսակին, և դա չի խափանի մնացած կոդը:

Դուք կարող եք անվերջ ավելացնել բաղադրիչների տեսակներ, բաղադրատոմսեր տարբեր տեսակի հրուշակեղենի համար, ստեղծել այլ դասեր, որոնք նկարագրում են հացաբուլկեղենները, հրուշակեղենի խանութները և նմանատիպ այլ ձեռնարկություններ:

Վատ արդյունք չէ։ Եվ այս ամենը շնորհիվ այն բանի, որ մենք փորձել ենք դասերը մինիմալ առնչություն ունենալ միմյանց հետ։

Հիմա եկեք մտածենք կախվածության ինվերսիայի սկզբունքի խախտման հետևանքների մասին.

  1. կոշտություն (համակարգում որևէ փոփոխություն կատարելը շատ դժվար կլինի, քանի որ յուրաքանչյուր փոփոխություն ազդել է դրա շատ տարբեր մասերի վրա):
  2. փխրունություն (երբ համակարգի մի մասում որևէ փոփոխություն է կատարվում, դրա մյուս մասերը դառնում են խոցելի, և երբեմն դա առաջին հայացքից այնքան էլ ակնհայտ չէ):
  3. անշարժություն (կարող եք մոռանալ այլ համակարգերում կոդի վերօգտագործման մասին, քանի որ մոդուլները խիստ փոխկապակցված են):

Դե, հիմա ինքներդ եզրակացություններ արեք, թե որքանով է օգտակար կախվածության ինվերսիայի սկզբունքը կոդում: Կարծում եմ՝ պատասխանն ակնհայտ է.

14 պատասխան

Հիմնականում ասվում է.

  • Աբստրակցիաները երբեք չպետք է կախված լինեն մանրամասներից: Մանրամասները պետք է կախված լինեն աբստրակցիաներից:

Ինչ վերաբերում է նրան, թե ինչու է դա կարևոր, մի խոսքով. փոփոխությունը ռիսկային է, և կախված հայեցակարգից, այլ ոչ թե իրականացումից, դուք նվազեցնում եք փոփոխությունների անհրաժեշտությունը զանգերի կայքերում:

Արդյունավետորեն, DIP-ը նվազեցնում է կոդի տարբեր մասերի միջև կապը: Գաղափարն այն է, որ թեև կան, ասենք, լոգերի ներդրման բազմաթիվ եղանակներ, այն օգտագործելու եղանակը պետք է համեմատաբար կայուն լինի ժամանակի ընթացքում: Եթե ​​դուք կարող եք արդյունահանել ինտերֆեյս, որը ներկայացնում է գրանցման հայեցակարգը, այդ ինտերֆեյսը ժամանակի ընթացքում պետք է շատ ավելի կայուն լինի, քան դրա իրականացումը, և զանգող կայքերը պետք է շատ ավելի քիչ ենթակա լինեն այն փոփոխություններին, որոնք դուք կարող եք կատարել՝ պահպանելով կամ ընդլայնելով այդ գրանցման մեխանիզմը:

Քանի որ իրականացումը ինտերֆեյսի հատուկ է, դուք հնարավորություն ունեք գործարկման ժամանակ ընտրել, թե որն է լավագույնս համապատասխանում ձեր կոնկրետ միջավայրին: Կախված դեպքից, սա նույնպես կարող է հետաքրքիր լինել։

Agile Software Development, Principles, Patterns and Practices և Agile Principles, Patterns and Practices C#-ում գրքերը լավագույն ռեսուրսներն են՝ ամբողջությամբ հասկանալու համար Կախվածության ինվերսիոն սկզբունքի հիմքում ընկած սկզբնական նպատակներն ու դրդապատճառները: «Կախվածության հակադարձման սկզբունքը» հոդվածը նույնպես լավ ռեսուրս է, բայց քանի որ այն նախագծի խտացված տարբերակն է, որն ի վերջո հայտնվել է նախկինում նշված գրքերում, թողնում է որոշ կարևոր քննարկումներ փաթեթի սեփականության հայեցակարգի վերաբերյալ։ և ինտերֆեյսները, որոնք առանցքային են Այս սկզբունքը տարբերվում է «ծրագրի ինտերֆեյսի համար, այլ ոչ թե իրականացման» վերաբերյալ ավելի ընդհանուր խորհուրդներից, որոնք ներկայացված են Դիզայնի ձևանմուշներ գրքում (Գամմա և այլն):

Համառոտ ամփոփման համար կախվածության ինվերսիայի սկզբունքը հիմնականում նպատակ ունի փոփոխությունԱվանդաբար «ավելի բարձր մակարդակի» բաղադրիչներից կախվածությունը «ստորին մակարդակի» բաղադրիչների ուղղում, այնպես որ «ցածր մակարդակի» բաղադրիչները կախված են ինտերֆեյսներից, պատկանող«Բարձր մակարդակի» բաղադրիչներին (Նշում. «Բարձր մակարդակի» բաղադրիչն այստեղ վերաբերում է արտաքին կախվածություններ/ծառայություններ պահանջող բաղադրիչին, և պարտադիր չէ, որ դրա հայեցակարգային դիրքը շերտավոր ճարտարապետության մեջ): Այնուամենայնիվ, հարաբերությունները չեն նվազում էայնքան, որքան նա տեղաշարժերայն բաղադրիչներից, որոնք տեսականորեն ավելի քիչ արժեք ունեն, մինչև տեսականորեն ավելի արժեքավոր բաղադրիչները:

Սա ձեռք է բերվում բաղադրիչների նախագծմամբ, որոնց արտաքին կախվածությունն արտահայտվում է որպես միջերես, որի համար բաղադրիչի սպառողը պետք է իրականացնի: Այլ կերպ ասած, որոշ ինտերֆեյսներ արտահայտում են այն, ինչ անհրաժեշտ է բաղադրիչին, այլ ոչ թե ինչպես եք օգտագործում բաղադրիչը (օրինակ՝ «INeedSomething», այլ ոչ թե «IDoSomething»):

Այն, ինչ չի անդրադառնում Կախվածության ինվերսիոն սկզբունքի վրա, ինտերֆեյսների միջոցով կախվածությունները վերացականացնելու պարզ պրակտիկան է (օրինակ՝ MyService -> ): Թեև սա անջատում է բաղադրիչը կախվածության կոնկրետ կատարման մանրամասնությունից, այն չի շրջում սպառողի և կախվածության միջև կապը (օրինակ՝ ⇐ Լոգեր.

Կախվածության ինվերսիայի սկզբունքի կարևորությունը կարելի է ամփոփել մեկ նպատակի մեջ՝ ծրագրային բաղադրիչները վերօգտագործելու կարողություն, որոնք իրենց ֆունկցիոնալության մի մասի համար հիմնվում են արտաքին կախվածությունների վրա (գրանցում, ստուգում և այլն):

Վերօգտագործման այս ընդհանուր նպատակի շրջանակներում մենք կարող ենք առանձնացնել կրկնակի օգտագործման երկու ենթատեսակ.

    Օգտագործելով ծրագրային բաղադրիչ մի քանի հավելվածներում՝ կախվածության իրականացումներով (օրինակ, դուք մշակել եք DI կոնտեյներ և ցանկանում եք մուտքագրել, բայց չեք ցանկանում կապել ձեր կոնտեյները որոշակի լոգերի հետ, այնպես որ բոլորը, ովքեր օգտագործում են ձեր կոնտեյները, պետք է օգտագործեն նաև գրանցումը ձեր ընտրած գրադարանը):

    Ծրագրային բաղադրիչների օգտագործումը զարգացող համատեքստում (օրինակ, դուք մշակել եք բիզնես տրամաբանության բաղադրիչներ, որոնք նույնն են մնում հավելվածի տարբեր տարբերակներում, որտեղ իրականացման մանրամասները զարգանում են):

Բազմաթիվ հավելվածներում բաղադրիչների վերօգտագործման առաջին դեպքում, օրինակ՝ ենթակառուցվածքի գրադարանում, նպատակն է սպառողներին տրամադրել հիմքում ընկած ենթակառուցվածքը՝ առանց ձեր սպառողներին կապելու ձեր սեփական գրադարանի կախվածություններին, քանի որ նման կախվածություններից կախվածություն ստանալը պահանջում է, որ սպառողները նաև պահանջեն. նույն կախվածությունները. Սա կարող է խնդրահարույց լինել, երբ ձեր գրադարանի սպառողները որոշեն օգտագործել այլ գրադարան նույն ենթակառուցվածքի կարիքների համար (օրինակ՝ NLog-ը և log4net-ը), կամ եթե նրանք որոշեն օգտագործել պահանջվող գրադարանի ավելի ուշ տարբերակը, որը անհամատեղելի է պահանջվող տարբերակի հետ։ ձեր գրադարանի կողմից:

Բիզնեսի տրամաբանական բաղադրիչների վերօգտագործման երկրորդ դեպքում (այսինքն՝ «Բարձր մակարդակի բաղադրիչներ») նպատակն է մեկուսացնել հավելվածի իրականացումը հիմնական շրջանակում ձեր կատարման մանրամասների փոփոխվող կարիքներից (օրինակ՝ մշտական ​​գրադարանների փոփոխություն/թարմացում, գրադարանների փոխանակման հաղորդագրություններ) . գաղտնագրման ռազմավարություններ և այլն): Իդեալում, հավելվածի իրականացման մանրամասները փոխելը չպետք է կոտրի այն բաղադրիչները, որոնք ներառում են հավելվածի բիզնես տրամաբանությունը:

Նշում. Ոմանք կարող են դեմ լինել այս երկրորդ դեպքը որպես փաստացի վերաօգտագործում նկարագրելուն՝ հավատալով, որ բաղադրիչները, ինչպիսիք են բիզնես տրամաբանության բաղադրիչները, որոնք օգտագործվում են մեկ զարգացող հավելվածում, կազմում են միայն մեկ օգտագործում: Այնուամենայնիվ, այստեղ գաղափարն այն է, որ հավելվածի իրականացման մանրամասների յուրաքանչյուր փոփոխություն ներկայացնում է նոր համատեքստ և, հետևաբար, օգտագործման այլ դեպք, թեև վերջնական նպատակները կարելի է առանձնացնել որպես մեկուսացում և տեղափոխելիություն:

Թեև երկրորդ դեպքում կախվածության ինվերսիայի սկզբունքին հետևելը կարող է որոշակի օգուտ ունենալ, հարկ է նշել, որ դրա կարևորությունը, ինչպես կիրառվում է ժամանակակից լեզուների համար, ինչպիսիք են Java-ն և C#-ը, զգալիորեն նվազել է, գուցե այն աստիճան, որ դա անտեղի է: Ինչպես արդեն քննարկվել է, DIP-ը ներառում է իրականացման մանրամասների ամբողջական տարանջատում առանձին փաթեթների մեջ: Զարգացող հավելվածի դեպքում, սակայն, բիզնես տիրույթի տերմիններով սահմանված ինտերֆեյսների օգտագործումը կպաշտպանի ավելի բարձր մակարդակի բաղադրիչները փոփոխելու անհրաժեշտությունից՝ պայմանավորված իրականացման մանրամասների բաղադրիչների փոփոխվող կարիքներից, նույնիսկ եթե իրականացման մանրամասները, ի վերջո, գտնվում են նույն փաթեթում։ . Սկզբունքի այս հատվածն արտացոլում է այն ասպեկտները, որոնք առնչվում էին լեզվին դրա կոդավորման պահին (օրինակ՝ C++), որոնք առնչություն չունեն ավելի նոր լեզուների համար։ Այնուամենայնիվ, կախվածության հակադարձման սկզբունքի կարևորությունը հիմնականում կապված է բազմակի օգտագործման ծրագրային բաղադրիչների/գրադարանների մշակման հետ:

Այս սկզբունքի ավելի մանրամասն քննարկումը, քանի որ այն վերաբերում է ինտերֆեյսի պարզ օգտագործմանը, կախվածության ներարկմանը և բաժանված ինտերֆեյսի օրինակին, կարելի է գտնել:

Երբ մենք մշակում ենք ծրագրային հավելվածներ, մենք կարող ենք դիտարկել ցածր մակարդակի դասեր՝ դասեր, որոնք իրականացնում են հիմնական և առաջնային գործողություններ (սկավառակի մուտք, ցանցային արձանագրություններ և այլն) և բարձր մակարդակի դասեր՝ դասեր, որոնք ներառում են բարդ տրամաբանություն (բիզնեսի հոսքեր,... ) .

Վերջիններս ապավինում են ցածր մակարդակի դասերին։ Նման կառույցների իրագործման բնական ճանապարհը կլինի ցածր մակարդակի դասեր գրելը և ամեն անգամ, երբ հարկադրված լինենք գրել բարդ բարձր մակարդակի դասեր։ Քանի որ բարձր մակարդակի դասերը սահմանվում են ուրիշների տեսանկյունից, սա կարծես տրամաբանական միջոց է դա անելու: Բայց սա արձագանքող դիզայն չէ: Ի՞նչ կլինի, եթե մեզ անհրաժեշտ լինի փոխարինել ցածր մակարդակի դասը:

Կախվածության հակադարձման սկզբունքը նշում է, որ.

  • Բարձր մակարդակի մոդուլները չպետք է կախված լինեն ցածր մակարդակի մոդուլներից: Երկուսն էլ պետք է կախված լինեն վերացականությունից:

Այս սկզբունքը նպատակ ունի «շրջել» սովորական գաղափարը, որ ծրագրային ապահովման բարձր մակարդակի մոդուլները պետք է կախված լինեն ավելի ցածր մակարդակի մոդուլներից: Այստեղ բարձր մակարդակի մոդուլները պատկանում են աբստրակցիային (օրինակ՝ ինտերֆեյսի մեթոդների որոշում), որոնք իրականացվում են ավելի ցածր մակարդակի մոդուլների կողմից։ Այսպիսով, ավելի ցածր մակարդակի մոդուլները կախված են ավելի բարձր մակարդակի մոդուլներից:

Կախվածության հակադարձման արդյունավետ օգտագործումը ապահովում է ճկունություն և կայունություն ձեր հավելվածի ճարտարապետության մեջ: Սա թույլ կտա ձեր հավելվածը զարգանալ ավելի ապահով և կայուն:

Ավանդական շերտավոր ճարտարապետություն

Ավանդաբար, շերտավորված ճարտարապետության ինտերֆեյսը կախված էր բիզնես շերտից, որն իր հերթին կախված էր տվյալների հասանելիության շերտից:

Դուք պետք է հասկանաք շերտը, փաթեթը կամ գրադարանը: Տեսնենք, թե ինչպես է ընթանում կոդը:

Մենք կունենայինք գրադարան կամ փաթեթ տվյալների հասանելիության շերտի համար:

// DataAccessLayer.dll հանրային դաս ProductDAO ( )

// BusinessLogicLayer.dll՝ օգտագործելով DataAccessLayer; հանրային դասի ProductBO (մասնավոր ProductDAO productDAO;)

Շերտավոր ճարտարապետություն՝ կախվածության հակադարձմամբ

Կախվածության հակադարձումը ցույց է տալիս հետևյալը.

Բարձր մակարդակի մոդուլները չպետք է կախված լինեն ցածր մակարդակի մոդուլներից: Երկուսն էլ պետք է կախված լինեն վերացականությունից:

Աբստրակցիաները չպետք է կախված լինեն մանրամասներից: Մանրամասները պետք է կախված լինեն աբստրակցիաներից:

Որո՞նք են բարձր և ցածր մակարդակի մոդուլները: Մտածելով այնպիսի մոդուլների մասին, ինչպիսիք են գրադարանները կամ փաթեթները, բարձր մակարդակի մոդուլները կլինեն նրանք, որոնք ավանդաբար ունեն կախվածություն, իսկ ցածր մակարդակը, որից կախված է:

Այլ կերպ ասած, մոդուլի բարձր մակարդակը կլինի այն վայրը, որտեղ կոչվում է գործողությունը, և ցածր մակարդակը, որտեղ գործողությունը կատարվում է:

Այս սկզբունքից կարելի է խելամիտ եզրակացություն անել՝ հանգույցների միջև չպետք է լինի կախվածություն, այլ պետք է լինի կախվածություն աբստրակցիայից։ Բայց մեր որդեգրած մոտեցման համաձայն՝ մենք գուցե սխալ ենք օգտագործում ներդրումային կախվածությունը, բայց դա վերացականություն է:

Պատկերացրեք, որ մենք հարմարեցնում ենք մեր կոդը այսպես.

Մենք կունենանք գրադարան կամ փաթեթ տվյալների հասանելիության շերտի համար, որը սահմանում է աբստրակցիան:

// DataAccessLayer.dll հանրային ինտերֆեյս IProductDAO հանրային դաս ProductDAO՝ IProductDAO( )

Եվ գրադարանի կամ փաթեթի մակարդակի բիզնես տրամաբանություն, որը կախված է տվյալների հասանելիության շերտից:

// BusinessLogicLayer.dll՝ օգտագործելով DataAccessLayer; հանրային դասի ProductBO (մասնավոր IProductDAO productDAO;)

Թեև մենք կախված ենք վերացականությունից, բիզնեսի և տվյալների հասանելիության միջև կապը մնում է նույնը:

Կախվածության ինվերսիային հասնելու համար կայունության միջերեսը պետք է սահմանվի այն մոդուլում կամ փաթեթում, որտեղ գտնվում է բարձր մակարդակի տրամաբանությունը կամ տիրույթը, այլ ոչ թե ցածր մակարդակի մոդուլում:

Նախ սահմանեք, թե ինչ է տիրույթի շերտը, և դրա հաղորդակցության աբստրակցիան սահմանվում է համառությամբ:

// Domain.dll հանրային ինտերֆեյսի IProductRepository; օգտագործելով DataAccessLayer; հանրային դասի ProductBO (մասնավոր IPProductRepository productRepository;)

Երբ կայունության մակարդակը կախված է տիրույթից, այժմ հնարավոր է շրջել, եթե կախվածություն է սահմանվում:

// Persistence.dll հանրային դաս ProductDAO՝ IProductRepository( )

Սկզբունքի խորացում

Կարեւոր է լավ հասկանալ հայեցակարգը՝ խորացնելով նպատակն ու օգուտը։ Եթե ​​մենք մնանք մեխանիկայի մեջ և ուսումնասիրենք տիպիկ պահեստը, մենք չենք կարողանա որոշել, թե որտեղ կարող ենք կիրառել կախվածության սկզբունքը:

Բայց ինչու ենք մենք շրջում կախվածությունը: Ո՞րն է կոնկրետ օրինակներից դուրս հիմնական նպատակը:

Սա սովորաբար թույլ է տալիս առավել կայուն բաները, որոնք անկախ են պակաս կայուն բաներից, ավելի հաճախ փոխվել:

Ավելի հեշտ է փոխել համառության տեսակը կամ տվյալների բազան կամ տեխնոլոգիան նույն տվյալների բազա մուտք գործելու համար, քան տիրույթի տրամաբանությունը կամ գործողությունները, որոնք նախատեսված են համառության հետ հաղորդակցվելու համար: Սա հանգեցնում է կախվածության շրջմանը, քանի որ ավելի հեշտ է փոխել համառությունը, եթե այդ փոփոխությունը տեղի ունենա: Այս կերպ մենք ստիպված չենք լինի փոխել տիրույթը: Դոմենի շերտը բոլորից ամենակայունն է, ուստի այն չպետք է կախված լինի որևէ բանից։

Բայց կա ավելին, քան պարզապես պահեստավորման այս օրինակը: Կան բազմաթիվ սցենարներ, որոնցում կիրառվում է այս սկզբունքը, և կան ճարտարապետություններ, որոնք հիմնված են այս սկզբունքի վրա:

ճարտարապետություն

Կան ճարտարապետություններ, որոնցում կախվածության ինվերսիան դրա սահմանման բանալին է: Բոլոր տիրույթներում սա ամենակարևորն է, և հենց աբստրակցիաները կսահմանեն տիրույթի և մնացած փաթեթների կամ գրադարանների միջև կապի արձանագրությունը:

Մաքուր ճարտարապետություն

Ինձ համար պաշտոնական հոդվածում նկարագրված կախվածության ինվերսիայի սկզբունքը

C++-ի խնդիրն այն է, որ վերնագրի ֆայլերը սովորաբար պարունակում են մասնավոր դաշտերի և մեթոդների հայտարարություններ: Այսպիսով, եթե բարձր մակարդակի C++ մոդուլը պարունակում է վերնագրի ֆայլ ցածր մակարդակի մոդուլի համար, դա կախված կլինի իրականից իրականացումըայս մոդուլի մանրամասները: Եվ սա ակնհայտորեն այնքան էլ լավ չէ: Բայց սա խնդիր չէ ավելի ժամանակակից լեզուներում, որոնք սովորաբար օգտագործվում են այսօր:

Բարձր մակարդակի մոդուլները, ըստ էության, ավելի քիչ վերօգտագործելի են, քան ցածր մակարդակի մոդուլները, քանի որ առաջինները սովորաբար ավելի շատ կիրառական/համատեքստային են, քան երկրորդները: Օրինակ, բաղադրիչը, որն իրականացնում է UI-ի էկրանը, ամենաբարձր մակարդակն է և նաև շատ (ամբողջովին) հատուկ հավելված: Նման բաղադրիչը մեկ այլ հավելվածում կրկին օգտագործելու փորձը հակաարդյունավետ է և կարող է հանգեցնել միայն գերզարգացման:

Այսպիսով, A բաղադրիչի նույն մակարդակում առանձին աբստրակցիա ստեղծելը, որը կախված է B բաղադրիչից (որը կախված չէ A-ից) կարող է կատարվել միայն այն դեպքում, եթե բաղադրիչ A-ն իրականում օգտակար կլինի տարբեր հավելվածներում կամ համատեքստերում վերօգտագործման համար: Եթե ​​դա այդպես չէ, ապա DIP հավելվածը վատ դիզայն կլինի:

Կախվածության ինվերսիայի սկզբունքը սահմանելու ավելի պարզ միջոց.

Ձեր մոդուլները, որոնք ներառում են բարդ բիզնես տրամաբանությունը, չպետք է ուղղակիորեն կախված լինեն այլ մոդուլներից, որոնք ներառում են բիզնես տրամաբանությունը: Փոխարենը, դրանք պետք է կախված լինեն միայն պարզ տվյալների միջերեսներից:

Այսինքն՝ ձեր տրամաբանական դասը գործադրելու փոխարեն, ինչպես սովորաբար անում են մարդիկ.

Դասի կախվածություն (...) դասի տրամաբանություն (մասնավոր կախվածություն; int doSomething() (// Բիզնեսի տրամաբանությունը՝ օգտագործելով dep այստեղ))

դուք պետք է անեք նման բան.

Դասի կախվածություն (...) ինտերֆեյս Data (...) դասը DataFromDependency-ն իրականացնում է Տվյալներ (մասնավոր կախվածություն; ...) դասի տրամաբանություն (int doSomething(Տվյալների տվյալներ) (// հաշվարկել ինչ-որ բան տվյալների հետ))

Data-ը և DataFromDependency-ը պետք է ապրեն նույն մոդուլում, ինչ Logic-ը, այլ ոչ թե Dependency-ով:

Ինչու սա?

Լավ պատասխաններ և լավ օրինակներ արդեն տրվել են այստեղ ուրիշների կողմից:

Կախվածության հակադարձման իմաստը ծրագրակազմը կրկին օգտագործելի դարձնելն է:

Գաղափարն այն է, որ երկու կոդերի փոխարեն, որոնք հենվում են միմյանց վրա, նրանք ապավինում են ինչ-որ վերացական ինտերֆեյսի: Այնուհետև կարող եք նորից օգտագործել ցանկացած մաս առանց մյուսի:

Սա սովորաբար ձեռք է բերվում կառավարման կոնտեյների (IoC) հետ շրջելով, ինչպիսին Spring-ն է Java-ում: Այս մոդելում օբյեկտների հատկությունները սահմանվում են XML կոնֆիգուրացիայի միջոցով, այլ ոչ թե օբյեկտները դուրս են գալիս և գտնում իրենց կախվածությունը:

Պատկերացրեք այս կեղծ կոդը...

Հանրային դաս MyClass ( հանրային ծառայություն myService = ServiceLocator.service; )

MyClass-ը ուղղակիորեն կախված է ինչպես Service դասից, այնպես էլ ServiceLocator դասից: Սա պահանջվում է երկուսի համար էլ, եթե ցանկանում եք այն օգտագործել մեկ այլ հավելվածում: Հիմա պատկերացրեք սա...

Հանրային դաս MyClass ( հանրային ISservice myService; )

MyClass-ն այժմ օգտագործում է մեկ ինտերֆեյս՝ ISservice ինտերֆեյսը: Մենք թույլ կտանք, որ IoC կոնտեյները իրականում սահմանի այս փոփոխականի արժեքը:

Թող լինի հյուրանոց, որը սննդամթերք արտադրողից կխնդրի իր պաշարները։ Հյուրանոցը սննդի գեներատորին տալիս է սննդի անվանումը (ասենք հավ), իսկ Գեներատորը վերադարձնում է խնդրված սնունդը հյուրանոցին։ Բայց հյուրանոցին չի հետաքրքրում սննդի տեսակը, որը ստանում և մատուցում է: Այսպիսով, Գեներատորը հյուրանոցին մատակարարում է «Սնունդ» պիտակով սնունդ:

Այս իրականացումը JAVA-ում

FactoryClass գործարանային մեթոդով: Սննդի գեներատոր

Public class FoodGenerator ( Food food; public Food getFood(String name)( if(name.equals("fish"))( food = new Fish(); )else if(name.equals("chicken"))( food = նոր հավ (); )այլ սնունդ = զրոյական; վերադարձ սնունդ; ))

Դասի անոտացիա/ինտերֆեյս

Հանրային վերացական դաս Food ( //Երեխաների դասերից ոչ մեկը չի անտեսի այս մեթոդը՝ որակ ապահովելու համար... public void quality())( String fresh = "Սա թարմ է" + getName(); String tasty = "Սա է համեղ " + getName(); System.out.println(թարմ); System.out.println(համեղ); ) հանրային աբստրակտ String getName();)

Հավը վաճառում է սնունդ (բետոնի դասի)

Public class Chicken extends Food ( /*Սննդի բոլոր տեսակները պետք է լինեն թարմ և համեղ, որպեսզի * Նրանք չեն հաղթահարի սուպեր դասի մեթոդը «property()»*/ public String getName())( վերադարձ «Հավ»; ))

Ձուկը վաճառում է սնունդ (հատուկ դաս)

Հանրային դասի Fish extensions Food ( /*Սննդի բոլոր տեսակները պետք է լինեն թարմ և համեղ, որպեսզի * Նրանք չեն հաղթահարի սուպեր դասի մեթոդը «property()»*/ public String getName()) ( վերադարձ «Fish»; ))

Վերջապես

Հյուրանոց

Հանրային դասի հյուրանոց ( public static void main (String args) ( //Using a Factory class…. «հավ»); սնունդ. որակ (); ) )

Ինչպես տեսնում եք, հյուրանոցը չգիտի՝ հավ է, թե ձուկ: Հայտնի է միայն, որ սա սննդամթերք է, այսինքն. Հյուրանոցը կախված է սննդի դասից:

Դուք կարող եք նաև նկատել, որ Fish and Chicken դասը իրականացնում է Food դասը և անմիջականորեն կապված չէ հյուրանոցի հետ: դրանք. հավի միսը և ձուկը նույնպես կախված են սննդի դասից:

Սա նշանակում է, որ բարձր մակարդակի բաղադրիչը (հյուրանոց) և ցածր մակարդակի բաղադրիչը (ձուկ և հավ) կախված են աբստրակցիայից (սնունդ):

Սա կոչվում է կախվածության ինվերսիա:

The Dependency Inversion Principle (DIP) ասում է, որ

i) Բարձր մակարդակի մոդուլները չպետք է կախված լինեն ցածր մակարդակի մոդուլներից: Երկուսն էլ պետք է կախված լինեն վերացականությունից:

ii) Աբստրակցիաները երբեք չպետք է կախված լինեն մանրամասներից: Մանրամասները պետք է կախված լինեն աբստրակցիաներից:

Հանրային ինտերֆեյս ICustomer ( string GetCustomerNameById(int id); ) public class Հաճախորդ՝ 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.id. ) ) public class Program ( static void Main() ( CustomerBLL customerBLL = new CustomerBLL(); int customerId = 25; string customerName = customerBLL.GetCustomerNameById(customerId); Console.WriteLine(customerName); Console);(.)Key

Նշում. Դասը պետք է կախված լինի աբստրակցիաներից, ինչպիսիք են ինտերֆեյսը կամ վերացական դասերը, այլ ոչ թե կոնկրետ մանրամասներից (ինտերֆեյսի իրականացում):

կիսվել

Վերջին թարմացումը՝ 03/11/2016

Կախվածության ինվերսիայի սկզբունքըԿախվածության հակադարձման սկզբունքը օգտագործվում է թույլ զուգակցված միավորներ ստեղծելու համար, որոնք հեշտ է փորձարկել, փոփոխել և թարմացնել: Այս սկզբունքը կարելի է ձևակերպել հետևյալ կերպ.

Վերին մակարդակի մոդուլները չպետք է կախված լինեն ավելի ցածր մակարդակի մոդուլներից: Երկուսն էլ պետք է կախված լինեն վերացականությունից:

Աբստրակցիաները չպետք է կախված լինեն մանրամասներից: Մանրամասները պետք է կախված լինեն աբստրակցիաներից:

Սկզբունքը հասկանալու համար հաշվի առեք հետևյալ օրինակը.

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 (տեքստ); )

Book դասը, որը ներկայացնում է գիրք, օգտագործում է ConsolePrinter դասը տպելու համար: Այս սահմանմամբ Book դասը կախված է ConsolePrinter դասից։ Ավելին, մենք խստորեն սահմանել ենք, որ գիրք տպել կարելի է միայն վահանակի վրա՝ օգտագործելով ConsolePrinter դասը։ Այլ տարբերակներ, օրինակ՝ տպիչի ելք, ֆայլի ելք կամ գրաֆիկական ինտերֆեյսի որոշ տարրեր օգտագործելը, այս դեպքում այս ամենը բացառված է: Գրքի տպագրության աբստրակցիան առանձնացված չէ ConsolePrinter դասի մանրամասներից: Այս ամենը կախվածության ինվերսիայի սկզբունքի խախտում է։

Հիմա եկեք փորձենք մեր դասերը համապատասխանեցնել կախվածության ինվերսիայի սկզբունքին՝ բաժանելով աբստրակցիաները ցածր մակարդակի իրականացումից.

Ինտերֆեյս IPprinter ( void Print (string text); ) class Book ( public string Text ( get; set; ) public IPprinter Printer ( get; set; ) public Book (IPprinter printer) ( this.Printer = printer; ) public void Print( ) ( Printer.Print(Text); ) ) դաս ConsolePrinter. IPprinter ( public void Print (string text) ( Console.WriteLine ("Print to console"); ) ) class HtmlPrinter: IPprinter ( public void Print(string text) ( Console.WriteLine («Տպել html-ով»); ) )

Գրքերի տպագրական աբստրակցիան այժմ առանձնացված է կոնկրետ իրականացումներից։ Արդյունքում և՛ Book դասը, և՛ ConsolePrinter դասը կախված են IPprinter աբստրակցիայից: Բացի այդ, այժմ մենք կարող ենք նաև ստեղծել IPprinter աբստրակցիայի լրացուցիչ ցածր մակարդակի իրականացումներ և դինամիկ կերպով կիրառել դրանք ծրագրում.

Գրքի գիրք = նոր Գիրք (new ConsolePrinter()); book.Print(); book.Printer = new HtmlPrinter(); book.Print();