Inversion of Control principle (IoC), Dependency Inversion principle (DIP) и Dependency Injection (DI)

Dependency Injection (DI)

Когато можем да инджектваме даден обект в друг обект.

Така се реализира design принципът Inversion of Control, демек – на практика реализиране на Single Responsibility SOLID принципът.

Types of Dependency Injection
  1. Constructor Injection – през конструктора, демек инджектваният обект се подава oще на конструктора на основният клас и се сетва като пропръти още при инстанциране на основният обект.
  2. Property (Setter) Injection – идеята е подобна на горната, пак инджектваният обект да бъде сетнат на клас пропърти, но не през конструктора, а през сетър.
  3. Method Injection – тук имаме ситуация, подобна на Property (Setter) Injection но с разликата, че основният клас имплементира интерфейс, който го задължава (него, основният) да има метод, който да играе ролята на сетър, и по този начин задължава основният клас да приеме даден обект.
    Например клас Car имплементира интерфейс EngineMountable, в който има метод engine() и по този начин класът Car e длъжен да има инджектнат обект за двигател.
    Добър въпрос би бил, ако Car наследява например абсклас, не може ли в него да е изнесен абстрактен сетер/сетери, които да играят същата роля, тоест вместо интерфейс да имаме абсклас.
    Не съм съвсем наясно с идеята на този тип injection, признавам си. Може би идеята е да се зададе някакъв централизиран начин за инджектване на обекти, ако имаме абсклас, или вероятно ако е интерфейс, да задължи имплементиращите обекти да имат такива „entry points“ за инджектване на обекти…

Inversion of Control (IoC)

Inversion of Control (IoC) е desing principle, която използва DI, при който идеята е части от цялостната функционалност на даден клас, да се „разхвърля“ или разпредели по други класове, с цел да се спази SOLID Single Responsibility Principle и основният клас да бъде олекотен, и след това когато ни трябват тези „разхвърляни“ функционалности, обекти на тези класове да бъдат инджектвани в основният клас.

Taка се постига и high cohesion вътре в класа както и low coupling между отделните класове.

Dependency Inversion Principle (DIP)

Dependency Inversion Principle (DIP) е SOLID принцип, нещо като продължение или разширение на IoC, в смисъл такъв, че повелява основният клас, в който ще инджектваме обектите на класовете, които сме „разхвърляли“, да може да приема като входни параметри не обекти от конкретен клас, а от различни класове, които обаче екстендват или имплементират даден абсклас или интерфейс.
Демек, „дай ми нещо, с което да отвинтя тази гайка, а не конкретен гаечен ключ“…

Идеята и на двата принципа е да се постигне максимално т.н. „loose coupling“ и максимална кохезия вътре в класа. Припомнете си статията „Cohesion and Coupling“.
Но работата е е там, че само с прилагане на IoC не става, демек само IoC не стига за да се постигне това.

Тоест, основният клас сякаш казва предварително: „не искам конкретна функционалност и обект от конкретен клас, давайте ми обекти, които по принцип ще ми свършат работата“, демек имплементиращи даден интерфейс.

Taка постигаме „high-level modules should not depend on low-level modules“.

Защо?
Защото в случая, high-level модулът при нас е основният клас, а low-level са тези, които му инджектваме, които вършат някаква по-конкретна и специфична работа, тоест са по-ниско в нивото на абстракция.
И основният става по-малко зависим от инджектваните, защото може да приема по-широк диапазон от обекти, а не конкретно само от един клас.

Пример: ти си основният клас и искаш да си викнеш (инджектнеш) майстор да ти смени водомера.
Кога си по-малко зависим (бидейки high-level module) от майстора (който е low-level module)? Ако трябва да можеш да викаш само един конкретен майстор или когато можеш да извикаш всеки майстор, стига да имплементира VodoprovodchikInterface?

Общото между тях?

Общото между IoC и DIP е целта – да се намали зависимостта между отделните класове. Например, да не се създава обект от един клас вътре в друг клас, защото така твърдо свързваме двата класа и единият вече не може без другият, конкретно „другият“, то е ясно, че все някакъв обкет трябва да му инджектнеш, освен ако не си му задал default стойност null.

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

Your code gets decoupled so you can easily exchange implementations of an interface with alternative implementations.

Разликата между тях?

Разликата е, че DIP e по-скоро продължение, надстройка на IoC. Демек, IoC повелчва функционалността на един клас да се „разхвърля“ на под-класове, а DIP повелява това разхвърляне да стане така, че „разхвърляният“ клас да може да приема не конкретни функционалноти, а функционалности по принцип.

Литература:

https://www.tutorialsteacher.com/ioc/introduction

https://www.tutorialsteacher.com/ioc/dependency-inversion-principle

https://www.tutorialsteacher.com/ioc/dependency-injection