DI - Dependency Inversion Principle
Принципът на инверсия на зависимостта (DIP) представлява част от обектно ориентираните принципи SOLID
DIP е проста – но въпреки това мощна – програмна парадигма, която можем да използва, за внедряване на добре структурирани, силно независими софтуерни компоненти за многократна употреба.
Когато не се използва DI софтуерните компоненти са плътно обвързани един с друг. Оттук те трудно се използват повторно, заменят и тестват, което води до сложни за подобрение дизайни.
Софтуера трябва да отговаря на следните правила за да изпълнява DI:
Модулите на високо ниво не трябва да зависят от нископоставените модули. И двете трябва да зависят от абстракции.
Абстракциите не трябва да зависят от подробности. Подробностите трябва да зависят от абстракциите
Oсновата на DIP е да обръщане на класическата зависимост между компонентите на високо ниво и ниско ниво, като се абстрахират от взаимодействието помежду си.
В традиционната разработка на софтуер компонентите на високо ниво зависят от такива от ниско ниво. По този начин е трудно да използвате повторно компонентите на високо ниво.
Използване на DIP
Следния пример се състой от клас StringProcessor, който получава стойност String с помощта на компонент на StringReader, и го записва с помощта на компонент на StringWriter:
Реализацията на този клас може да стане по няколко начина:
StringReader и StringWriter, компонентите на ниско ниво, са твърдо зададени класове, поставени в една и същи пакет. StringProcessor, компонентът на високо ниво се поставя в различен пакет. StringProcessor зависи от StringReader и StringWriter. Няма инверсия на зависимости, оттам StringProcessor не може да се използва повторно в различен контекст.
StringReader и StringWriter са интерфейси, поставени в една и същи пакет заедно с класа който ги имплементира. StringProcessor сега зависи от абстракции, но компонентите на ниско ниво не. Все още не сме постигнали инверсия на зависимостите.
StringReader и StringWriter са интерфейси, поставени в един и същи пакет заедно с StringProcessor. Сега, StringProcessor има изрична собственост върху абстракциите. StringProcessor, StringReader и StringWriter всички зависят от абстракции. Постигнахме инверсия на зависимости от горе до долу, като абстрахиране на взаимодействието между компонентите. StringProcessor сега е многократна за използване в различен контекст.
StringReader и StringWriter са интерфейси, поставени в отделен пакет от StringProcessor. Постигнахме инверсия на зависимостите и също така е по-лесно да заменим имплементациите на StringReader и StringWriter. StringProcessor също е много по използваем за различени контексти.
От всички горепосочени сценарии само елементи 3 и 4 са валидни внедрявания на DIP.
Точка 3 е пряко DIP изпълнение, където компонентът на високо ниво и абстракциите са поставени в един и същи пакет. От тук компонентът на високо ниво притежава абстракциите. При това изпълнение компонентът на високо равнище отговаря за определянето на абстрактния протокол, чрез който взаимодейства с компонентите на ниско ниво.
По същия начин т. 4 е по-добро DIP изпълнение. В този вариант на модела нито компонентът на високо ниво, нито нископоставените имат собствеността върху абстракциите.
Абстракциите се поставят в отделен слой, което улеснява превключването на компонентите на ниско ниво. В същото време всички компоненти са изолирани един от друг, което дава по-силна капсулиация.
Избор на правилното ниво на абстракция
В повечето случаи изборът на абстракциите, които компонентите на високо ниво ще използват, трябва да бъде доста ясен, но с едно предупреждение, което си заслужава да се отбележи: нивото на абстракция.
В примера по-горе използвахме DI, за да инжектираме тип StringReader в класа StringProcessor. Това би било ефективно, стига нивото на абстракция на StringReader да е близо до домейна на StringProcessor.
За разлика от това, просто бихме пропуснали присъщите ползи на DIP, ако StringReader например е обект на файл, който чете стойност на String от файл. В този случай нивото на абстракция на StringReader би било много по-ниско от нивото на домейна на StringProcessor.
Казано по-просто, нивото на абстракция, което компонентите на високо ниво ще използват, за да си сътрудничат с тези от ниско ниво, трябва винаги да са близо до домейна на първите.
Пряко изпълнение на DIP
Нека разгледаме следния пример, който да предоставя услуги на клиенти:
Основното хранилище на слоя обикновено е база данни, но за да поддържаме кода прост, тук ще използваме обикновена колекция.
Нека започнем с определяне на компонента на високо ниво:
CustomerService класът прилага методите findById() и findAll() , които донасят клиентите от по нисък слои.
В този случай типът CustomerDao е абстракцията, която CustomerService използва за консумиране на компонента от ниско ниво.
Тъй като това е директно DIP изпълнение, нека определим абстракцията като интерфейс в същия пакет на CustomerService:
Поставяйки абстракцията в същия пакет на компонента на високо ниво, правим компонента отговорен за притежаването на абстракцията. Този детайл на внедряване е това, което наистина обръща зависимостта между компонента на високо ниво и този на ниско ниво.
Освен това нивото на абстракция на CustomerDao е близо до тази на CustomerService, която се изисква и за добър DIP.
Сега, нека създадем компонента на ниско ниво в различен пакет. В този случай това е просто основно внедряване на CustomerDao:
Тази реализация позволява класа SimpleCustomerDao да бъде подменян прио промяна на изискването без това да влияе на класа от високо ниво, което улеснява процеса по тестване и внедряване на софтуера.
Last updated
Was this helpful?