Какво представлява Dependency Injection (DI)

Този пост е инспириран от коментар в предишен пост където започна лека дискусия относно създаването на нови инстанции на обекти в самите обекти. Но нека да започнем от самото начало:

Какво точно е DI

DI е част от общо приети добри практики в програмирането подобно на S.O.L.I.D, които не са абсолютни и задължителни, но съобразяването с тях, доколкото е възможно, води до по-добра архитектура, намалява „спагети код“, премахва скритите зависимости, позволява по-доброто преизползване на код и много други хубави работи. D в S.O.L.I.D е за  Dependency Injection.

D в S.O.L.I.D е за Dependency inversion, което е различно от DI.
Много имплементации обединяват Dependency Injection и Dependency Inversion в едно.

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

class Payment{    
    public function __construct(){
        $this->gataway = new PayPal();
        $this->db = new MySQLDB();
        $this->invoice = new Invoice();
    }
} 

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

И точно тук имаме огромен проблем, самият клас Payment създава всички свой зависимости в себе си, които от своя страна може да имат свой собствени зависимости. Това е изключително лоша практика поради много причини, но някой от основните са, че ние сме създали пряка зависимост – този клас за да работи трябва да има достъп до другите класове с техния namespacе и тяхната конкретна имплементация. Този клас може да бъде използван само в тази „среда“, ако решим да го изнесем като библиотека ще трябва да вземем и останалите класове от които зависи и да задължим потребителя който ще го използва да използва и нашите класове за DB, Invoice, PayPal. Това е кошмарно, никой уважаващ себе си софтуерен архитект не би допуснал това да се случи.

Другият, според мен, по-голям проблем е, че няма лесен начин за подмяна на имплементация. Какво ще стане ако имаме нужда в определена ситуация да използваме друг payment процесор, примерно Stripe? Останалата логика е еднаква, разликата е кой ще обработи плащането. Единия вариант е да правим различни „магии“ с наследяването или не дай си боже с рефлекции и магически методи (не го правете, така ще се застреляте, че няма оправяне). Другата опция е следната „класика“:

class Payment{    
    public function __construct($gataway){
        if($gataway=="PayPal"){
            $this->gataway = new PayPal();
        }
        else{
            $this->gataway =n ew Stripe();
        }        
        $this->db=new MySQLDB();
        $this->invoice = new Invoice();
    }
}

Изглежда сякаш проблемът е решен, но не е. Даже напротив, създали сме още по-лоша ситуация при която страдаме от всички недостатъци на предишния подход но сме добавили и допълнителни зависимости. Не е особено трудно да си представите какво ще стане с този конструктор след няколко итерации на продукта, добавяне на нови изисквания и имплементации – конструктор с прекалено много IF/ELSE и тотален кошмар при поддръжка, разширяване и тестване.

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

Значи излиза, че проблемът е в създаването на обекти в самия обект, добре, какво ще стане ако тези зависимости ги предаваме като параметри:

class Payment{    
    public function __construct($gataway,$db,$invoice){            
        $this->gataway = $gataway
        $this->db = $db;
        $this->invoice = $invoice;
    }
}

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

Кода написан по следния начина по-горе има и друг проблем, специфичен за слабо типизираните езици като PHP – конструктора приема някаква променлива, но тя може да е всичко, не сме задължили предаването на точно определен тип. Какво ще стане ако за $db се подаде String? Ами всичко ще гръмне. Конкретно в PHP това се решава така:

class Payment{    
    public function __construct(PayPal $gataway,MySQLDB $db,Invoice $invoice){            
        $this->gataway = $gataway
        $this->db = $db;
        $this->invoice = $invoice;
    }
}

Така задължаваме използването на точно определени класове, не може да подадем String или нещо друго в конструктора. Обаче все още не е решен друг проблем – какво правим ако искаме да сменим имплементацията, примерно искаме да използваме Stripe? Не може да подадем Stripe обект защото конструктора очаква PayPal обект. Едната опция, която в някой частни случай е добра, но като цяло трябва да се избягва е да имаме общ базов клас:

class PaymentProcessor{
    
    public function pay(){}
}

class PayPal extends PaymentProcessor{}

class Stripe extends PaymentProcessor{}

class Payment{
    
    public function __construct(PaymentProcessor $gataway,MySQLDB $db,Invoice $invoice){            
        $this->gataway = $gataway
        $this->db = $db;
        $this->invoice = $invoice;
    }
}

Забележете, че вече Payment не очаква Paypal или Stripe, а очаква PaymentProcessor. По този начин, ако някой реши да си прави негов си процесор трябва само да наследи базовия клас PaymentProcessor и всичко ще работи, защото ние гарантирано, че метода pay() ще е наличен. Искам отново да отбележа, че това са изкуствено опростени примери, в реална ситуация може би ще е по-добре да се използва абстрактен клас, но всичко зависи от конкретните нужди.

Изглежда има напредък, махнахме зависимостите от самия обект, също така позволихме подмяна на имплементацията използвайки базови класове, но и това не е идеален вариант. Все още има зависимост към базовия клас – PaymentProcessor. Ако искаме да изнесем класа си в библиотека ще трябва да включим и PaymentProcessor заедно с неговите зависимости. Може да не ви се струва много, но колкото по-малко зависимости имаме, толкова по-добре. При по-комплексни системи тези „малки“ зависимости ескалират много неприятно.

Кодиране спрямо интерфейс

Нека да помислим какво е необходимо на класа Payment, като се съсредоточим конкретно за payment процесора, същите правила важат и а останалите класове.

Единственото необходимо е инстанцията която се предава да има серия от точно определени методи за да гарантираме, че когато Payment се опита да извика метода pay() той ще съществува. Как може да го направим като не използваме базови/абстрактни класове? Ами много е просто – интерфейси.

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

interface PaymentProcessor{
    
    public function pay(){}
}

class PayPal implements PaymentProcessor{}

class Stripe implements PaymentProcessor{}

class Payment{
    
    public function __construct(PaymentProcessor $gataway,MySQLDB $db,Invoice $invoice){            
        $this->gataway = $gataway
        $this->db = $db;
        $this->invoice = $invoice;
    }
}

Разгледайте кода по-горе внимателно, разликите са много малки, но много съществени. Създаваме интерфейс PaymentProcessor който задължава всеки клас който го имплеметира да има метода pay(). След което казваме на класа Payment, че ще приема само и единствено обекти, които са имплементирали интерфейса PaymentProcessor. По този начин е гарантирано, че обекта ще има метода pay() и няма да изгърми с МethodNotFound  или подобно.

Но най-голямото предимство на кодирането спрямо интерфейс е, че ние нямаме зависимост спрямо имплементация. Когато се използва базов/абстрактен клас ние имаме серия от методи, които имат конкретна имплементация (в базовия клас) и задължаваме всички да се съобразяват с нея. А това е излишно. Единственото от което се интересува класа Payment е да има обект, който има метод pay() който ще извърши плащането и евентуално ще върне резултат. Каква е имплементацията на pay() няма значение.

И сега ако искаме да изнесем класа си в библиотека ще е лесно, ще трябва да включим само интерфейсите и никаква конкретна задължаваща имплеменация. Това е основата на така наречения Dependency Inversion, някъде може да срещнете абревиатурата IoC (Inversion of Control) което е същото нещо, като изключим семантичните разлики и дефиниции, но това не е DI, това е техника която работи много добре с DI.

Още проблеми

До момента решихме един от проблемите, а именно как да премахнем ненужните зависимости като кодираме спрямо интерфейс, и това само по себе си е много добре, но все още остава сложността по създаването на обектите – за да направим нова инстанция на Payment ще трябва да направим нова инстанция на класове за PaymentProcessor, Invoice и DB. На пръв поглед това не е кой знае какъв проблем, все пак всичко е ясно и стриктно. Обаче не е точно така. Нека разгледаме следната ситуация, на много места в програмата ни ще имаме нужда от база данни, и ако сме добри програмисти ще сме кодирали базата данни спрямо интерфейс, и там където трябва база данни ще си правим конкретна инстанция и ще я предаваме на различните обекти. Примерно:

interface iDB{
    
    public function select(){}
    public function insert(){}
}

class MySQLDB implements iDB{}

class PostgresqlDB implements iDB{}

class User{
    public function __construct(iDB $db){}
}

class Account{
    public function __construct(iDB $db){}
}

$db = new MySQLDB();
$user = new User($db);
$account = new Account($db);

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

Правим инстанция на конкретен клас и го предаваме на всички обекти, по този начин, ако решим в един момент да използваме PostgresqlDB само ще трябва да сменим инстанцията. Обаче на практика нещата не са толкова прости. Имаме стотици обекти със своите зависимости, ако трябва да сменим имплементацията ще трябва да отидем ръчно да я сменим на много места. Предполагам веднага се досещате, че може да използваме нещо като Registry или „глобална“ променлива, която ще държи инстанцията на DB. Тоест, само на едно място правим new iDB и всички които зависят от нея използват тази „променлива“. Така ще се наложи да сменим кода само на едно място. Звучи логично, но практиката сочи друго.

Използването на глобални променливи и Registry само по себе си води до проблеми, но най-вече ако се избере този подход какво правим, ако различните класове имат нужда от различни инстанции. Един и същи клас, просто различни инстанции. Понеже държим само една инстанция в глобална променлива (или някъде другаде) всички ще ползват една и съща инстанция, което често е нежелано.

Също така често се случва да правим инстанция на клас и да предаваме зависимостите му, които обаче също имат зависимости, които също имат зависимости….

Много бързо може да попаднем в ситуация при която е много трудно да напишем код за нова инстанция. Не защото нещо не е ясно, а защото е много „замотано“ и управлението на всички тези инстанции може лесно да се превърне в една голяма безумна серия от IF/ELSE забутани в някой файл с коментар от типа на:

While writing this code only me and god knew hоw it works. Now only god knows

DI в действие

DI е механизъм, който (най-често) използва кодиране спрямо интерфейси за да оправи цялата сложност по създаването на новите инстанции и тяхното инжектиране. Различните езици и библиотеки имат различни имплементации, но всички се свеждат до следните прости стъпки:

  • Пишете си кода спрямо интерфейси
  • Декларирайте зависимости на обект като опишете интерфейсите в конструктора на обекта
  • Дефинирайте коя конкретна имплементация трябва да се използва е момента (най-често в конфигурационен файл) и решете какво да е поведението и, дали да е само една, или всеки път да се прави нова инстанция

Това е, нищо сложно, понеже DI контейнера се грижи за всичко останало.

Ще се опитам да дам донякъде реалистичен (силно опростен) пример от света на PHP и Laravel как точно се случват нещата, но всички езици и frameworks имат множество DI библиотеки, които се различават и имат различни възможности.

interface SessionStorage{
  public function get( $key );
  public function set( $key, $value );
}

class FileSession implements SessionStorage{

  public function get( $key ){}

  public function set( $key, $value ){}
}

class MysqlSession implements SessionStorage{

  
  public function get( $key ){}

  public function set( $key, $value ){}
}

class Session{

  protected $session;

  public function __construct( SessionStorage $session ){
    $this->session = $session;
  }
}

App:bind( 'SessionStorage', 'MysqlSession' );

$session = $this->app->make('Session');

Нека да видим какво точно се случва тук. Първо правим интерфейс какво трябва да може да прави  всеки един SessionStorage клас. След това декларираме конкретните имплементации. До тук е ясно. Магията е в последните два реда.

App:bind( 'SessionStorage', 'MysqlSession' );

Тук казваме на Laravel, че когато нещо някъде има нужда от SessionStorage ще използва MysqlSession

$session = $this->app->make('Session');

А така караме DI контейнера да направи нова инстанция на Session, която зависи от интерфейса SessionStorage. В този случай обекта Session ще получи автоматично MysqlSession в конструктора си. Ако в последствие решим, че искаме да използваме FileSession ще трябва да сменим само един ред:

App:bind( 'SessionStorage', 'FileSession' );

Разбира се, това е много елементарен пример, но показва ясно колко работа може да си спестим. Този подход работи много добре с тестване, continuous integration и автоматизация, защото кое какво използва се определя на едно място, което може да е конфигурационен файл.

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

Някой от вас ще си кажат, че това може да го постигнете и с нещо като Factory. Има допирни точки, но при Factory шаблона трябва да се опишете всичко на ръка, което ще доведе до безумна серия от IF/ELSE или нещо подобно. С DI този процес е автоматизиран. Също така в PHP света (и не само) има тенденция да се правят спецификации и стандарти за интерфейси. Примерно какви са интерфейсите за кеширане, за HTTTP заявки и подобни, и всички библиотеки започват да пишат спрямо тях. При използване на DI и общи интерфейси може да подмените огромна подсистема само с един ред код.

Недостатъци на DI

Това не е панацея, DI има своите проблеми и недостатъци, но ситуацията е, че тези недостатъци са по-малкото зло, а и някой от тях може да се премахнат или намалят използвайки добри практики. Ето някой от основните критики.

Кода ни е силно зависим от самата DI библиотека Не може лесно да сменим DI контейнера. Иронично DI, който се бори със зависимостите, създава зависимост. Но според мен това не винаги е проблем, от гледна точка, че DI е част от framework-а който използваме, Ако имаме причина да подменяме целия framework явно имаме по-големи проблеми от DI контейнера.

Често може да се загубим от интерфейси, особено при по-големи проекти. Решението на този проблем е добра софтуерна архитектура – нещо което така или иначе трябва да се прави независимо от езика и проекта.

Честа критика е, че DI реално скрива сложността и създава нов слой между частите на приложението ни. Еми, то тази сложност трябва да отиде някъде, нека поне да е на място където може да се контролира.

Изисква повече знания  – ами то сложен професионален проект така или иначе си ги изисква.

Забавя производителността – понеже DI ползва (най-често) рефлекция има известно забавяне понеже всеки обект трябва да се провери, да се види от какво зависи и да се вземе конкретната имплементация. В зависимост от конкретната реализация това време може да е доста сериозно. Също така има нужда от малко повече RAM. Но добрите DI контейнери са силно оптимизирани и отнемат само няколко милисекунди. Може да видите сравнение между различните DI в света на PHP  в този много детайлен пост (цък)

Заключение

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

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

Дори и да не се използва DI, писането спрямо интерфейси ще е полезно за проекта за напред. Много хора критикуват Java, че прекаляват с OOP и интерфейсите, но това е начина по който може да държите проект от няколко милиона реда код 10 години, който се пише от 1000 различни екипа и все още да работи и да се развива.

Разбира се, ако не сте фенове може да минете на чисто функционално програмиране 🙂

Уточнение:

В статията разглеждам две концепции, Dependency Injection и Dependency Inversion. Двете работят много добре заедно, но не е задължително винаги да се използват в комбинация. Може да имаме Dependency Injection без да пишем спрямо интерфейси, а примерно с абстрактни класове, също така може да пишем спрямо интерфейси и да не използваме Dependency Injection.

20 мнения по „Какво представлява Dependency Injection (DI)

  1. gatakka Автор

    Есрествено, всеки в сектора трябва да знае кой е uncle Bob 🙂

  2. Stilgar

    Аз не мисля, че е много трудно човек да подмени DI. Аз оня ден подменях. Вярно трябва да си препишеш конфигурацията, но това не е голяма мъка. Проблем става когато се ползва нещо дето не е constructor injection или поне не е стандартизирано като специфични за контейнера атрибути/анотации дето са нацвъкани по класовете, но така или иначе тея неща най-добре да се избягват. Ако човек си inject-ва през конструктора както би трябвало да се прави грижи няма.

    Също така това за производителността са глупутки. Пишеш web на PHP (че и на Java да е все тая), не съществува вариант в реален проект DI да ти е значителен bottleneck. Преди да срещнеш проблем с DI сигурно ще искаш да мигрираш към Hack и HTTP2 и още 1000 неща дето причиняват несравнимо по-голямо забавяне от DI. Все пак не създаваш полигони в 3D engine в game loop.

  3. gatakka Автор

    Относно Dependency Injection vs Dependency Inversion коментара е изключително коректен. В грешка съм (коригирано е в поста).
    Разликата между двете идей е че Dependency Inversion изисква да пишем спрямо интерфейс, или ако трябва да сме формални „Модулите от високо ниво не трябва да зависят от модулите на по-ниско ниво. И двата трябва да зависят от абстракция. Абстракцията не трябва да зависи от детайлите, а детайлите трябва да зависят от абстракцията“. Тази част я разгледах подробно.
    Dependency Injection е механизма по който се инжектира конкретната имплементация спрямо абстрактната, тоест това е нещото, което върши работата.

    Двете идеи работят много добре заедно, но не е задължително. Може да имаме injection без да пишем спрямо интерфейси (а с абстрактни класове), както и може да пишем спрямо интерфейси без да използваме injection.
    Това може би не стана много ясно в статията, че засягам 2 теми.

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

    А за скоростта, DI на Zend добавя 1 секунда забавяне при 1000 заявки, в света на PHP, където отговор над 400ms се смята за бавно е много голямо забавяне. Но пък другите добавят само няколко милисекунди. Нямам си ина идея как стоят нещата при C# или Java, но заради естеството на езиците мога да предположа, че там ще е дори по-малко времето, освен ако някой „гении“ не се е развихрил с пълна сила 🙂

  4. PaperNick

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

    Благодаря за уточнението и достъпния начин, по който представяш информацията!

  5. Stilgar

    Според това сравнение за .NET най-бавните контейнери инстанцират 500 000 обекта за 190 секунди, което е 0.38 секунди за 1000 обекта. Всичко това се случва в сценария „complex“ което е „Objects with several nested dependencies are resolved“. На Transient (най-често срещаното) този дето е най-бавен на complex дава 24 секунди за 500 000 обекта което прави само 48 милисекунди за 1000 обекта. И това подчертавам е един от най-бавните. Бързите са около 1000 пъти по-бързи. Въобще тея Zend не знам как са го постигнали това, но дори при това положение практически в никой request не се инстанцират 1000 зависимости, предполагам 1 request на сложна web страница инстанцира около 50. Вярно, че да си добавиш 50ms само заради смотан IoC не е добре ама то пък може да се ползва друг, а не че концепцията за DI е бавна.

  6. Ivan Tcholakov

    Да приемем хамалогията при смяна на DI контейнера. Но има и още един случай: ако искаме да сложим в проекта компоненти, пригодени за различни DI контейнери. Има един проект по въпроса https://github.com/jeremeamia/acclimate-container , не съм го пробвал, но го имам предвид като резервна възможност.

  7. sHoRtBG

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

  8. Димитър

    Използвал съм DI (за сесии , рутери и т.н.) и наистина е много удобно и гъвкаво , защото се инициализират веднъж

    $db = new MySQLDB();
    $user = new User($db);
    $account = new Account($db);

    „Едва ли някой някога някъде ще реализира DB по този начин“

    Както казваш едва ли някой ще реализира DB по този начин , но да приемем че имаме ситуация в която в контролерите трябва да инстанцираме „многократно“ даден клас които изисква DI , примерно класа
    class Payment{

    public function __construct(PaymentProcessor $gataway,MySQLDB $db,Invoice $invoice){
    $this->gataway = $gataway
    $this->db = $db;
    $this->invoice = $invoice;
    }
    }

    е как трябва да използвам Payment , всеки път да му подавам един тон параметри или да направя метод/клас фабрика който да ми връща обекта Payment . Премахваме зависимостите отвътре и ги слагаме отвън ?!

    А какво ще стане ако инстанцирането на даден обект , изисква Инжектиране на обект който също изисква Инжектиране на друг обект ?!

  9. Михаил

    Идеята за DI наистина е хубава, някак си започнах да работя наистина обектно. Но има нещо, което не ми харесва. Ако ни трябват различни инстанции на класове в различни методи от класа, в който сме ги инжектирали в конструктора (public function __construct(Class1 $class1, Class2 $class2 , Class3 $class3)), то ние винаги имаме инстанциите на всички класове, даже и да не ни трябват. Има ли начин да се избегне създаването на инстанции на ненужните за даден метод класове?

  10. gatakka Автор

    TL;DR; Можеш ама недей 🙂

    @Михаил принципно може но е много лоша идея да се прави, ще създаде доста проблеми.
    Принципно класовете трябва да получат всички свои зависимости при създаването си, за да може в последствие да работят нормално и да няма null pointer и подобни изцепки, защото някой ресурс не е подаден.

    Това което ще спестиш е правенето на инстанция на един клас, но това (при всички нормални езици) е безумно малко време и заема безумно малко ресурси.

    Ако класовете алокират външни ресурси (примерно връзка за база или някаква мрежова комуникация) може да ги направиш „мързеливи“ – тоест когато направиш new DB() да не отваряш директно връзка към базата. Тя ще се отвори когато има нужда от нея, примерно когато се извика някой метод като fetch.

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

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

    И разбира се, най-големия проблем с който ще се сблъскаш – имаш клас който взима 3 зависимости (D1,D2,D3) метода M1 има нужда само от D1, ама метода M2 има нужда и от D1,D2 и D3, и се налага да извикаш и 2-та метода от една инстанция. Какво ще стане ако си подал само D1?
    Ще правиш втора инстанция за да използваш M2?

    Много омотано става 🙂

  11. Ivan T

    Аз пак да питам – един статичен анализатор на код как се оправя с dependency-тата дето се дават външно? Защото се губи връзката между класовете в кода.

  12. gatakka Автор

    Зависи много от езика, контейнера и анализатора. При качествените анализатори не би трябвало да има проблем, те проверяват изходния код, а не стойностите в runtime. Но зависи от езика. Конкретно за PHP до скоро нямаше нито един читав анализатор на код, но вече почнаха да се появяват за версия 7, след като вече PHP използва абстрактни синтактични дървета, които са много удобни за анализ.
    За Java не съм използвал анализатор, но съм коментирал с колеги, че там проблем няма.

  13. Ivan T

    „За Java не съм използвал анализатор, но съм коментирал с колеги, че там проблем няма.“

    Ползвах system101 и още 2-3 вида софт за статичен анализ на Java.
    Проблем има, категорично. Стига до рефлекция в кода, от рода на name.class и връзката в сорса се прекъсва, защото name се генерира в runtime.
    DI седи извън кода, описва се в разни конфигурационни XML-и, както и анотации в класовете. Аз не съм виждал статичен анализатор да може да взема предвид тия неща. Най-малкото защото всеки DI framework си има свои собствен набор анотации. А framewor-ци има сигурно 50 вида. DI e като различен вид език в проекта, и най-лошото е, че не е стандартен, като Java например.
    Дайте малко по-сериозно, може да не съм прав, но темата прави огромно объркване. Дори по интервюта сме спорили по въпроса, това DI идва като епидемия, а мнозинството от колегите не са запознати с чистата алтернатива. В жаварския случай – EJB.

  14. Ivan T

    А, да не забравя…
    Какво правим с дебъгера, като стигне до тая рефлекция? Моите наблюдения са, че спира дотам.
    Значи – в името на някакви измислени SOLID се лишаваме от основни тулове като статични анализатори за потенциални грешки, и графично изобразяване на йерархията. И още по-лошо – и от дебъгер.

  15. gatakka Автор

    @Ivan T какво има да се бърка статичен анализатор на кода с DI? Аз това не разбирам. Идеята на DI е да ти подмени имплементация в runtime спрямо дадени условия, ама кода който се подменя го има в source. Статичния анализатор би трябвало да се оправя. Не мога да коментирам за java, но когато аз съм ползвал статични анализатори на други езици не съм имал проблем.
    EJB? Ти сериозно ли сравняваш EJB и DI? Ми то нямат нищо общо, ама нищо общо. Не съм сериозен джавар, ама доста съм се блъскал с EJB-та (работих 2 години Java2EE) и нещо не виждам къде им е общото.

    А кой дебъгер ползваш, че не може да се оправи в runtime с DI, кажи ми за да знам никога да не го използвам, защото явно е прекалено счупен.

    Измислените SOLID са измислени с цел, те не решават всички проблеми, но емпирично е доказано, че тяхното използване води до по-добри резултати в дългосрочен план. Умни хора са ги мислили, още по-умни са ги тествали.

    Какво ти пречи DI за графично изобразяване на йерархията? Пускай си UML инструмента, давай си диаграмите, DI заменя имплементация а не характеристика. За това се пише спрямо интерфейси.

    И между другото, като не харесваш този принцип не го ползвай, никой не е опрял пистолет в главата ти и не те е оставил без избор 🙂
    Ама има много хора/проекти, които го намират за удобен и полезен, хубаво е да се знае и използва.

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

  16. Ivan T

    „Ти сериозно ли сравняваш EJB и DI? Ми то нямат нищо общо, ама нищо общо. “
    Не съм казал, че имат общо.
    Статичните анализатори се бъркат от външното свързване на DI, поне тия, дето съм работил с тях.
    Дебъгерът е на Еклипс.
    И аз чакам „сериозни жавари“.

  17. Pingback: Dependency Injection, Services and Containers (DIC) – My PHP Place

Вашият коментар

Вашият имейл адрес няма да бъде публикуван. Задължителните полета са отбелязани с *