Какъв е принципът на инверсия на зависимостта и защо е важен? Принцип на инверсия на зависимостта Инверсия на зависимостта

Всъщност всички принципи ТВЪРДса тясно свързани помежду си и основната им цел е да помогнат за създаването на висококачествен, мащабируем софтуер. Но последният принцип ТВЪРДнаистина се откроява срещу тях. Първо, нека разгледаме формулировката на този принцип. Така, принцип на инверсия на зависимостта (Принцип на инверсия на зависимостта - DIP): „Зависимост от абстракциите. Няма зависимост от нищо конкретно.. Известният експерт по разработка на софтуер, Робърт Мартин, също подчертава принципа DIPи го представя просто като резултат от следване на други принципи ТВЪРД— принципът отворен/затворен и принципът на заместване на Лисков. Спомнете си, че първият казва, че класът не трябва да се модифицира, за да прави нови промени, а вторият се занимава с наследяването и предполага безопасното използване на производни типове от някакъв базов тип, без да се нарушава правилната работа на програмата. Робърт Мартин първоначално формулира този принцип, както следва:

един). Модулите от по-високо ниво не трябва да зависят от модулите от по-ниско ниво. Модулите и на двете нива трябва да зависят от абстракциите.

2). Абстракциите не трябва да зависят от детайлите. Подробностите трябва да зависят от абстракциите.

Тоест, необходимо е да се разработят класове от гледна точка на абстракции, а не техните специфични реализации. И ако следвате принципите OCPи LSP, тогава точно това ще постигнем. Затова нека се върнем малко назад към урока. Там като пример разгледахме класа бард, който в самото начало беше свързан с класа китара, представляващ определен музикален инструмент:

публичен клас Bard (частна китара китара; публичен Bard(китара китара) ( this.guitar = китара; ) public void play() ( guitar.play(); ) )

публичен клас Bard(

частна китара китара;

публичен бард (китара китара)

това. китара = китара ;

публична празна игра()

китара. възпроизвеждане();

В случай, че искаме да добавим поддръжка за други музикални инструменти, тогава ще трябва по някакъв начин да променим този клас. Това е явно нарушение на принципа OCP. И може би вече сте забелязали, че това също са нарушения на принципа DIP, тъй като в нашия случай нашата абстракция се оказа зависима от детайли. От гледна точка на по-нататъшното разширяване на нашия клас това никак не е добре. За да може класът ни да отговаря на условията на принципа OCPдобавихме интерфейс към системата инструмент, който реализира специфични класове, представящи определени видове музикални инструменти.

Файл Instrument.java:

публичен интерфейс Инструмент (void play(); )

публичен интерфейс Инструмент(

voidplay();

Файл Китара.java:

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

клас Китара инструменти Инструмент(

@Override

публична празна игра()

Система. навън println("Свири на китара!");

Файл лютня.java:

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

публичен клас Lute изпълнява инструмент(

@Override

публична празна игра()

Система. навън println("Свири лютня!" );

След това сменихме класа бардза да можем при необходимост да заменим имплементациите с точно тези, които ни трябват. Това носи допълнителна гъвкавост на създаваната система и намалява нейната кохезия (силна класова зависимост един от друг).

публичен клас Bard ( private Instrument instrument; public Bard() ( ) public void play() ( instrument.play(); ) public void setInstrument(Instrument instrument) ( this.instrument = instrument; ) )

публичен клас Bard(

частен инструмент инструмент;

2 отговора

Добър момент - думата инверсия е донякъде изненадваща (тъй като след прилагане на DIP, модулът на зависимост от по-ниско ниво очевидно сега не зависи от модула на повикващия от по-високо ниво: или повикващият, или зависимият вече са по-свободно свързани чрез допълнителна абстракция).

Може да попитате защо използвам думата "инверсия". Честно казано, това е така, защото по-традиционните методи за разработка на софтуер като структуриран анализ и дизайн са склонни да създават софтуерни структури, в които модулите на високо ниво зависят от модулите на ниско ниво и в които абстракциите зависят от детайлите. Всъщност една от целите на тези методи е да се дефинира йерархия от подпрограми, която описва как модулите от високо ниво правят извиквания към модули от ниско ниво... По този начин структурата на зависимостта на една добре проектирана обектно-ориентирана програма е "обърнат" по отношение на структурата на зависимостта, която обикновено е резултат от традиционни процедурни методи.

Една точка, която трябва да се отбележи, когато четете статията на чичо Боб за DIP, е, че C++ няма (и към момента на писане няма) интерфейси, така че постигането на тази абстракция в C++ обикновено се постига чрез абстрактен/чист виртуален базов клас, докато в Java или C# абстракцията за разхлабване на свързването обикновено е да се развърже чрез абстрахиране на интерфейса от зависимостта и обвързване на модула(ите) от по-високо ниво към интерфейса.

редактиранеСамо да поясня:

„На някое място също виждам, че се нарича инверсия на зависимости“

Инверсия:Инвертирайте управлението на зависимостите от приложение към контейнер (като Spring).

Инжектиране на зависимост:

Вместо да пишете фабричен модел, какво ще кажете за инжектиране на обекта директно в клиентския клас. Така че нека позволим на клиентския клас да препраща към интерфейса и трябва да можем да инжектираме конкретния тип в клиентския клас. С това клиентският клас не трябва да използва новия ключова думаи напълно отделени от конкретни класове.

Какво ще кажете за инверсия на контрола (IoC)?

В традиционното програмиране потокът от бизнес логика се определя от обекти, които са статично присвоени един на друг. При инверсия на контрола потокът зависи от обектна графика, която се инстанцира от асемблера и става възможна чрез обектни взаимодействия, дефинирани чрез абстракции. Процесът на групиране се постига чрез инжектиране на зависимости, въпреки че някои твърдят, че използването на локатор на услуги също осигурява инверсия на контрола.

Инверсията на управлението като ръководство за проектиране служи за следните цели:

  • Има отделяне на изпълнението на определена задача от изпълнението.
  • Всеки модул може да се фокусира върху това, за което е предназначен.
  • Модулите не правят никакви предположения за това какво правят другите системи, а разчитат на своите договори.
  • Подмяната на модули не засяга други модули.

Вижте за повече информация.

Последна актуализация: 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. Всичко това е нарушение на принципа на инверсия на зависимостите.

Сега нека се опитаме да приведем нашите класове в съответствие с принципа на инверсия на зависимостта, като разделим абстракциите от изпълнението на ниско ниво:

Интерфейс 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("Печат в html"); ) )

Сега абстракцията на книгопечатането е отделена от конкретните реализации. В резултат както класът Book, така и класът ConsolePrinter зависят от абстракцията IPrinter. В допълнение, сега можем също да създадем допълнителни реализации на ниско ниво на абстракцията IPrinter и да ги прилагаме динамично в програмата:

Книга книга = нова книга (нов конзолен принтер()); book.Print(); book.Printer = нов HtmlPrinter(); book.Print();

14 отговора

Основно се казва:

  • Абстракциите никога не трябва да зависят от детайлите. Подробностите трябва да зависят от абстракциите.

Що се отнася до това защо това е важно, с една дума: промените са рискови и в зависимост от концепцията, а не от изпълнението, вие намалявате необходимостта от промяна в сайтовете за обаждания.

Ефективно DIP намалява връзката между различните части на кода. Идеята е, че въпреки че има много начини за внедряване, да речем, на логер, начинът, по който го използвате, трябва да бъде относително стабилен във времето. Ако можете да извлечете интерфейс, който представлява концепцията за регистриране, този интерфейс трябва да бъде много по-стабилен във времето от неговата реализация, а извикващите сайтове трябва да са много по-малко податливи на промени, които може да направите, като запазите или разширите този механизъм за регистриране.

Тъй като изпълнението зависи от интерфейса, можете да изберете по време на изпълнение кое изпълнение е най-добро за вашата конкретна среда. В зависимост от случая това също може да бъде интересно.

Книгите Agile Software Development, Principles, Patterns and Practices и Agile Principles, Patterns and Practices в C# са най-добрите ресурси за пълно разбиране на първоначалните цели и мотивации зад принципа на инверсия на зависимостите. Статията за принципа на обръщане на зависимостта също е добър ресурс, но поради това, че е съкратена версия на чернова, която се озова в споменатите по-горе книги, оставя някои важни дискусии относно концепцията за собственост на пакета и интерфейси, които са ключови за разграничаването на този принцип от по-общия съвет "програма за интерфейса, а не за изпълнението", открит в Design Patterns (Gamma, et al.).

За да обобщим, принципът на инверсия на зависимостта цели основно промянатрадиционната посока на зависимостите от компоненти от "по-високо ниво" към компоненти от "по-ниско ниво", така че компонентите от "по-ниско ниво" зависят от интерфейсите, притежаваниКомпоненти от „по-високо ниво“ (Забележка: Компонент от „по-високо ниво“ тук се отнася до компонент, който изисква външни зависимости/услуги, а не непременно неговата концептуална позиция в многослойна архитектура.) намаляваколкото и тя се изместваот компоненти, които са теоретично по-малко ценни, към компоненти, които са теоретично по-ценни.

Това се постига чрез разработване на компоненти, чиито външни зависимости са изразени като интерфейс, за който потребителят на компонента трябва да осигури имплементация. С други думи, дефинираните интерфейси изразяват това, от което се нуждае бобът, а не как го използвате (напр. "INeedSomething", а не "IDoSomething").

Това, за което принципът за обръщане на зависимостите не се отнася, е простата практика на абстрахиране на зависимости с интерфейси (като MyService → ). Въпреки че това отделя bean-а от конкретния детайл на изпълнението на зависимостта, то не обръща връзката между потребителя и зависимостта (напр. ⇐ Logger.

Значението на принципа на инверсия на зависимостите може да се сведе до една единствена цел - способността за повторно използване на софтуерни компоненти, които разчитат на външни зависимости за част от тяхната функционалност (регистрация, валидиране и т.н.)

В рамките на тази обща цел за повторна употреба можем да разграничим два подтипа повторна употреба:

    Използване на софтуерен компонент в множество приложения с реализации на зависимости (например, вие сте разработили 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 ( private IProductRepository productRepository; )

След като нивото на постоянство зависи от домейна, вече е възможно да се обърне, ако е дефинирана зависимост.

// Persistence.dll публичен клас ProductDAO: IProductRepository()

Задълбочаване на принципа

Важно е да разберете добре концепцията, задълбочавайки целта и ползите. Ако останем в механиката и изучаваме типично хранилище, няма да можем да определим къде можем да приложим принципа на зависимостта.

Но защо обръщаме зависимостта? Каква е основната цел извън конкретните примери?

Това обикновено е позволява най-стабилните неща, които не зависят от по-малко стабилните неща, да се променят по-често.

Типът постоянство е по-лесен за промяна, база данни или технология за достъп до същата база данни, отколкото логиката на домейна или действията, предназначени да бъдат свързани с постоянството. Поради това зависимостта е обърната, защото е по-лесно да се промени устойчивостта, ако тази промяна настъпи. По този начин няма да се налага да сменяме домейна. Слоят на домейна е най-стабилният от всички, така че не трябва да зависи от нищо.

Но не е само този пример за съхранение. Има много сценарии, при които този принцип се прилага, и има архитектури, базирани на този принцип.

архитектура

Има архитектури, при които инверсията на зависимостта е ключът към нейното дефиниране. Във всички домейни това е най-важното и именно абстракциите ще определят протокола за комуникация между домейна и останалите пакети или библиотеки.

Чиста архитектура

За мен принципът на инверсия на зависимостта е описан в официалната статия

Проблемът с C++ е, че заглавните файлове обикновено съдържат частни полета и декларации на методи. Така че, ако C++ модул от високо ниво съдържа заглавен файл за модул от ниско ниво, това ще зависи от действителния изпълнениеподробности за този модул. И това очевидно не е много добре. Но това не е проблем в по-модерните езици, които обикновено се използват днес.

Модулите от високо ниво са по своята същност по-малко многократно използвани от модулите от ниско ниво, тъй като първите обикновено са по-специфични за приложение/контекст от вторите. Например, компонентът, който внедрява екрана на потребителския интерфейс, е от най-високо ниво и също много (напълно?) специфичен за приложението. Опитът да се използва повторно такъв компонент в друго приложение е контрапродуктивен и може да доведе само до свръхразработка.

По този начин създаването на отделна абстракция на същото ниво на компонент А, което зависи от компонент Б (което е независимо от А), може да бъде направено само ако компонент А действително би бил полезен за повторно използване в различни приложения или контексти. Ако това не стане, тогава прилагането на DIP би било лош дизайн.

По-ясен начин да се посочи принципът на инверсия на зависимостта е:

Вашите модули, които капсулират сложна бизнес логика, не трябва да зависят директно от други модули, които капсулират бизнес логиката. Вместо това те трябва да зависят само от интерфейси до прости данни.

Т.е., вместо да имплементирате вашия клас Logic, както обикновено правят хората:

Класова зависимост ( ... ) класова логика ( частна зависимост dep; int doSomething() ( // Бизнес логика, използваща dep тук ) )

трябва да направите нещо като:

Class Dependency ( ... ) интерфейс Data ( ... ) class DataFromDependency внедрява данни ( private Dependency dep; ... ) class Logic ( int doSomething(Data data) ( // изчислява нещо с данни ) )

Data и DataFromDependency трябва да живеят в същия модул като Logic, а не с Dependency.

Защо е това?

добри отговори и добри примеривече дадени от други тук.

Целта на инверсията на зависимостта е да направи софтуера повторно използваем.

Идеята е, че вместо два кода да разчитат един на друг, те разчитат на някакъв абстрактен интерфейс. След това можете да използвате повторно всяка част без другата.

Това обикновено се постига чрез обръщане на контролен контейнер (IoC) като Spring в Java. В този модел свойствата на обектите се конфигурират чрез XML конфигурацията, вместо обектите да излизат и да намират своята зависимост.

Представете си този псевдокод...

Публичен клас MyClass ( публична услуга myService = ServiceLocator.service; )

MyClass директно зависи както от класа Service, така и от класа ServiceLocator. Това е необходимо и за двете, ако искате да го използвате в друго приложение. Сега си представете това...

Публичен клас MyClass ( public IService myService; )

MyClass сега използва един интерфейс, интерфейса IService. Ще позволим на IoC контейнера действително да задава стойността на тази променлива.

Нека има хотел, който да иска доставките си от производителя на храни. Хотелът дава името на храната (да речем пиле) на генератора на храни, а генераторът връща исканата храна на хотела. Но хотелът не се интересува от вида на храната, която получава и сервира. Така Генераторът доставя храна с надпис „Храна“ до хотела.

Тази реализация в JAVA

FactoryClass с фабричен метод. Генератор на храна

Публичен клас FoodGenerator ( Food food; public Food getFood(String name)( if(name.equals("fish"))( food = new Fish(); )else if(name.equals("chicken"))( food = new Chicken(); )else food = null; return food; ) )

Анотация/интерфейс на класа

Публичен абстрактен клас Food ( //Никой от дъщерния клас няма да замени този метод, за да гарантира качество... public void quality()( String fresh = "Това е прясно " + getName(); String tasty = "Това е вкусно " + getName(); System.out.println(fresh); System.out.println(tasty); ) публичен абстрактн низ getName(); )

Пилешки инструменти Храна (Клас бетон)

Public class Chicken extends Food ( /*Всички типове храни трябва да бъдат пресни и вкусни, така че * Те няма да заменят метода на супер класа "property()"*/ public String getName()( return "Chicken"; ) )

Риба продава храна (Клас бетон)

Публичен клас Fish разширява храната ( /*Всички типове храни трябва да бъдат пресни и вкусни, така че * Те няма да заменят метода на супер клас "property()"*/ public String getName()( return "Fish"; ) )

Накрая

хотел

Публичен клас Hotel ( public static void main(String args)( //Използване на клас Factory.... FoodGenerator foodGenerator = new FoodGenerator(); //Фабричен метод за инстанциране на храните... Food food = foodGenerator.getFood( "пиле"); food.quality(); ))

Както можете да видите, хотелът не знае дали е пиле или риба. Известно е само, че това е хранителен обект, т.е. Хотелът зависи от класа на храната.

Може също да забележите, че класът Fish and Chicken прилага класа Food и не е пряко свързан с хотела. тези. пиле и риба също зависи от класа храна.

Това означава, че компонентът на високо ниво (хотел) и компонентът на ниско ниво (риба и пиле) зависят от абстракцията (храна).

Това се нарича инверсия на зависимостта.

Принципът на инверсия на зависимостта (DIP) гласи това

i) Модулите от високо ниво не трябва да зависят от модулите от ниско ниво. И двете трябва да зависят от абстракциите.

ii) Абстракциите никога не трябва да зависят от детайли. Подробностите трябва да зависят от абстракциите.

Публичен интерфейс ICustomer ( низ GetCustomerNameById(int id); ) публичен клас Customer: ICustomer ( //ctor public Customer()() публичен низ GetCustomerNameById(int id) ( връщане на "Фактично име на клиент"; ) ) публичен клас CustomerFactory ( публичен статичен ICustomer GetCustomerData() ( връщане на нов клиент(); ) ) публичен клас CustomerBLL ( ICustomer _customer; публичен CustomerBLL() ( _customer = CustomerFactory.GetCustomerData(); ) публичен низ GetCustomerNameById(int id) ( връщане _customer.GetCustomerNameById(id); ) ) публичен клас Програма ( static void Main() ( CustomerBLL customerBLL = new CustomerBLL(); int customerId = 25; низ customerName = customerBLL.GetCustomerNameById(customerId); Console.WriteLine(customerName); Console.ReadKey(); ) )

Забележка. Един клас трябва да зависи от абстракции като интерфейс или абстрактни класове, а не от конкретни детайли (имплементация на интерфейс).

дял

Формулирането на принципа на инверсия на зависимостта се състои от две правила, спазването на които има изключително положителен ефект върху структурата на кода:

  • модулите от най-високо ниво не трябва да зависят от модулите от по-ниско ниво. И двете трябва да зависят от абстракцията.
  • абстракциите не трябва да зависят от детайлите. Подробностите трябва да зависят от абстракциите.

На пръв поглед не звучи много привлекателно и читателят вероятно вече се е подготвил за най-скучната статия с куп термини, сложни обороти и примери, от които така или иначе нищо не е ясно. Но напразно, защото по принцип, при спазване на принципа на инверсия на зависимостите, всичко отново се свежда до правилното използване и в контекста на основната идея - повторно използване на кода.

Друга концепция, която ще бъде от значение тук, е слабото обвързване на типовете, тоест намаляването или премахването на тяхната зависимост един от друг, което всъщност се постига с помощта на абстракция и полиморфизъм. Това всъщност е същността на принципа на инверсията на зависимостите.

Сега нека да разгледаме пример, който визуално ще демонстрира как изглежда свободното подвързване в действие.

Да кажем, че решим да поръчаме торта за рожден ден. За да направим това, отидохме в една прекрасна пекарна на ъгъла на улицата. Разбрахме дали могат да ни изпекат торта под гръмкото име „Лукс“ и след като получихме положителен отговор, я поръчахме. Всичко е просто.

А сега нека решим какви абстракции трябва да бъдат предоставени в този код. За да направите това, просто си задайте няколко въпроса:

  • Защо торта? Можете също да поръчате торта или кексчета.
  • Защо рожден ден? Ами ако беше сватба или дипломиране?
  • Защо точно „Лукс“ и какво, ако „Мравуняк“ или „Прага“ ми харесат повече?
  • Защо точно в тази пекарна, а не в сладкарница в центъра на града или някъде другаде?

И всяко от тези „какво ако“ и „какво ако“ са точките, в които се изисква разширяемост на кода и, съответно, свободно свързване и дефиниране на тип чрез абстракции.

Сега нека да разгледаме конструкцията на самите типове.

Нека дефинираме интерфейс за всеки сладкарски шедьовър, който можем да поръчаме.

Интерфейс IPastry ( име на низ ( get; set; ) )

А ето и едно конкретно изпълнение, за нашия случай - торта за рожден ден. Както можете да видите, традиционно тортата за рожден ден включва свещи)))

Клас BirthdayCake: IPastry ( public int NumberOfCandles ( get; set; ) public string Name ( get; set; ) public overriding string ToString() ( return String.Format("(0) with (1) nice candles", Name, NumberOfCandles ) ; ) )

Сега, ако имаме нужда от торта за сватба или просто за чай, или искаме кексчета или кремове, имаме основен интерфейс за всички.

Следващият въпрос е как сладкарските продукти се различават един от друг (с изключение на името, разбира се). Разбира се, рецептата!

А концепцията за рецепта включва списък на съставките и описание на процеса на готвене. Следователно за концепцията за съставка ще имаме отделен интерфейс:

Интерфейс IIngredient ( низ IngredientName ( get; set;) double Quantity ( get; set; ) string Units ( get; set; ) )

А ето и съставките за нашата торта: брашно, масло, захар и сметана:

Клас Flour:IIngredient ( публичен низ IngredientName ( get; set; ) public double Quantity ( get; set; ) public string Units ( get; set; ) public string Quality ( get; set; ) ) class Butter: IIngredient ( public string IngredientName ( get; set; ) public double Quantity ( get; set; ) public string Units ( get; set; ) ) class Sugar: IIngredient ( public string IngredientName ( get; set; ) public double Quantity ( get; set; ) public string Единици ( get; set; ) public string Kind ( get; set; ) ) class Creme: IIngredient ( public string IngredientName ( get; set; ) public double Quantity ( get; set; ) public string Units ( get; set; ) public двойна мазнина (вземете; задайте;))

Списъкът на съставките може да се различава в различните рецепти и различни сладкарски продукти, но за нашата рецепта този списък е достатъчен.

И сега е време да преминем към концепцията на рецептата. Каквото и да готвим, ние във всеки случай знаем как се казва, какво е, какви съставки са включени в ястието и как се приготвя.

Интерфейс IRecipe ( Тип PastryType ( get; set; ) низ Име ( get; set;) IList Съставки ( get;set;) низ Описание ( get;set;) )

По-конкретно, класът, представящ рецептата за торта за рожден ден, изглежда така:

Клас BirthdayCakeRecipe: IRecipe ( публичен Тип PastryType ( get; set; ) public string Име ( get; set;) public IList Съставки ( get; set; ) публичен низ Описание ( get; set; ) public BirthdayCakeReipe() ( Ingredients = new List (); } }

Сега да преминем към нашата прекрасна пекарна на ъгъла на улицата.

Разбира се, можем да приложим към много други пекарни, така че ще дефинираме основен интерфейс и за него. А кое е най-важното за една пекарна? Възможност за печене на продукти.

Интерфейс IBakery (IPastry Bake(IRecipe recipe); )

А ето и класът, представящ нашата пекарна:

Клас NiceBakeryOnTheCornerOFMyStreet ( Речник меню = нов речник (); public void AddToMenu(IRecipe recipe) ( if (!menu.ContainsKey(recipe.Name)) ( menu.Add(recipe.Name, recipe); ) else ( Console.WriteLine("Вече е в менюто"); ) ) public IRecipe FindInMenu(string name) ( if (menu.ContainsKey(name)) ( return menu; ) Console.WriteLine("Съжаляваме... в момента нямаме" + име); return null; ) public IPastry Bake (IRecipe recipe) ( if (recipe != null) ( IPastry pastry = Activator.CreateInstance(recipe.PastryType) as IPastry; if (pastry != null) ( pastry.Name = recipe.Name; return pastry as IPastry; ) ) върне нула; ))

Остава само да тестваме кода:

Програма за клас ( static void Main() ( //създаване на инктанс на класа пекарна var bakery = new NiceBakeryOnTheCornerOFMyStreet(); //подготвяне на съставките за рецептата var flour = new Flour() ( IngredientName = "Flour", Quantity = 1.5 , Units = "kg" ); var butter = new Butter() ( IngredientName = "Butter", Quantity = 0,5, Units = "kg" ); var sugar = new Sugar() ( IngredientName = "Sugar", Quantity = 0,7 , Units = "kg" ); var creme = new Creme() ( IngredientName= "Creme", Quantity = 1.0, Units = "liters"); //и това е самата рецепта var weddingCakeRecipe = new BirthdayCakeRecipe() ( PastryType = typeof(BirthdayCake), Name = "Луксозна торта за рожден ден", Description = "описание как да направите красива торта за рожден ден"); weddingCakeRecipe.Ingredients.Add(braшно); weddingCakeRecipe.Ingredients.Add(butter); weddingCakeRecipe.Ingredients .Add(sugar); weddingCakeRecipe.Ingredients.Add(creme); //добавяне на нашата рецепта за торта към менюто на пекарната bakery.AddToMenu(weddingCakeRec ipe); //нека сега да я поръчаме!! BirthdayCake cake = bakery.Bake(bakery.FindInMenu("Birthday Cake Luxury")) като BirthdayCake; //добавяне на няколко свещи ;) cake.NumberOfCandles = 10; //и ето ни !!!Console.WriteLine(cake); ) )

Сега нека отново да разгледаме целия код и да го оценим. Кодът е доста прост, типовете, техните абстракции, данни и функционалност са ясно очертани, кодът осигурява разширение и повторно използване. Всеки сегмент може безболезнено да бъде заменен с друг, съответстващ на основния тип, и това няма да доведе до срив на останалата част от кода.

Можете безкрайно да добавяте видове съставки, рецепти за различни видовесладкарски изделия, създайте други класове, които описват пекарни, сладкарниции други подобни заведения.

Не е лош резултат. И всичко това благодарение на факта, че се опитахме да направим класовете минимално свързани помежду си.

Сега нека помислим за последствията от нарушаване на принципа на инверсия на зависимостта:

  1. твърдост (би било много трудно да се направят промени в системата, тъй като всяка промяна засяга много различни части от нея).
  2. нестабилност (когато се направи някаква промяна в една част от системата, други части от нея стават уязвими и понякога това не е твърде очевидно на пръв поглед).
  3. неподвижност (можете да забравите за повторното използване на код в други системи, тъй като модулите са силно свързани помежду си).

Е, сега направете свои изводи до каква степен принципът на инверсия на зависимостите е полезен в кода. Мисля, че отговорът е очевиден.