Как услугите са повлияли на поведението на инжекционната зависимост на разработчиците - Flagbit Blog

Поне от постоянно нарастващата популярност на модулните тестове, особено чрез PHPUnit, разрешаването на зависимостите на класовете чрез инжектиране на зависимости в PHP играе все по-важна роля в ежедневието на разработчиците. Класовете, които имат една или повече зависимости от други типове класове, се прехвърлят, когато са създадени чрез конструктора или чрез извиквания на методи по време на изпълнение. Това подобрява поддържаемостта на отделните класове и зависимостите могат да се обменят по-лесно, ако изискванията се променят. Подобрена е и проверимостта на отделните компоненти, при която зависимостите от класа могат да бъдат заменени с прости двойни тестове. Недостатък обаче е увеличеният брой на шаблонния код, когато едни и същи обекти винаги трябва да бъдат създадени в различни точки на проекта, за да се покрият едни и същи зависимости.
Така нареченият локатор на услуги е въведен с текущите PHP рамки. Задачата на локатора на услуги е да разрешава и създава екземпляри зависимости от услуга вътрешно. Това означава, че екземпляри на обекти могат да бъдат създадени с помощта на услуга, без да се налага да добавяте кода на шаблона, за да разрешите зависимостите в бизнес логиката. Освен това, локаторът на услуги осигурява един екземпляр на извиканата услуга в рамките на заявка, за да се избегне ненужното създаване на обект без недостатъците на класическия сингълтън.
Въпреки това, лекотата на използване на Service Locator насочи използването на инжектиране на зависимости в насоки, които могат да бъдат внедрени бързо от техническа гледна точка, но които не съвсем следват целта му. Разтварянето на зависимостите се превърна в цветно сливане на класовете.
По-долу бих искал да представя накратко случаите, с които съм се сблъсквал от време на време. Обикновено те на пръв поглед може да не изглеждат погрешно, но могат да имат недостатъци при по-нататъшното развитие.
Инжектиране на локатор на услуги
Може би най-известният лош навик при използване на Локатор на услуги е да добавите това като зависимост в своя клас. Обикновено придружен от аргумента „Може би все още имам нужда от някоя от услугите“, този модел е по-скоро знак, че човек не е планирал достатъчно в своето развитие. Никой клас никога не се нуждае от всички услуги. Понякога можете да намерите и коментари в Google, които смятат, че е добре, ако на фабрика е даден Service Locator. Дори една фабрика се нуждае от малко услуги, за да функционира. Ако има зависимост от повече от 4 други услуги, трябва да помислите отново за задачата и структурата на фабриката.
Сложността е особено очевидна при писането на модулни тестове. Класът обикновено може да се използва за идентифициране на зависимостите от главата на метода на конструктора или методите за задаване. Когато се добави локатор на услуги, трябва да се създаде тестов двойник не само за него, но и за всички действителни зависимости, които се извличат от него. Също така е трудно да се разбере от кода какъв е действителният тип услуга. Тъй като клас в PHP може да съдържа и методи, които не идват от интерфейс, взаимозаменяемостта на класове със същия интерфейс вече не е гарантирана.
В идеалния случай зависимостите трябва да се прехвърлят, а не да се вземат от друг обект.
Ако погледнете например Symfony 2, ще видите, че Service Locator също се използва директно тук. Най-известните примери са контролерите и командите, но само ако те изпълняват интерфейса ContainerAware, какъвто е случаят като стандарт. Но има и опции за контролери и команди, за да се избегне това.
Регистрация
Регистрацията на информация може да бъде по-важна за един проект, отколкото за друг и може да се различава по количеството информация. Ето защо често съществува подходът за постоянно вмъкване на регистрационен екземпляр като зависимост в услугата на клас. В много малко случаи обаче регистраторът наистина е зависимост, тъй като първо трябва да си зададете въпрос: „Моята функция все още ли ще работи без регистратора?“
В зависимост от дизайнерското решение може да е достатъчно да се хвърли изключение в класа и да се добави запис в дневника в блок за хващане в допълнение към обработката на грешки.
Друга възможност е да съберете съобщенията, за да ги изведете по-късно, както можете напр. от валидатори. И тук записите в дневника могат да бъдат създадени извън класа и по този начин регистраторът може да бъде извлечен като зависимост.
Разбира се, това не е грубо нарушение на зависимостта, но ако е лесно да държите регистратора извън клас, тогава разработчикът трябва. Това е ненужен компонент в автоматизираните тестове и е без значение за функцията на тествания клас.
Твърде много зависимости
Всеки, който разглежда многото зависимости в конструктора на своя клас и се чувства неудобно с тях, вероятно е разпознал проблема. Простото боравене с конфигурации на услуги улеснява добавянето на нови зависимости, тъй като разработчикът обикновено не трябва да се притеснява от произтичащите промени при създаването на екземпляри. В резултат на това броят на зависимостите може да нараства с напредването на проекта, създавайки сложност, която не е склонна да се докосва и променя.
Много контейнери за впръскване на зависимости предлагат опцията за задаване на зависимости на услугата, използвайки методи за настройка в конфигурацията. Това означава, че някои разработчици са склонни да задават зависимости, като използват методи за задаване, вместо да използват конструктора, но това не решава проблема. Напротив: Setters трябва да се задават изрично и само когато обектът вече съществува. Използването на сетера е взето от конфигурацията на услугата, но този клас, напр. в модулни тестове, може да се използва само със знанието на тези залагащи и ако не са зададени задължителни зависимости, разработчикът също трябва да се погрижи за обработка на грешки. Това ненужно увеличава засегнатия клас. Нищо от това не е необходимо при инжектиране на зависимост чрез конструктора. И така, как трябва да се справим с този проблем?
Това е добър знак, че класът прави повече, отколкото би трябвало.
В този случай техните задачи трябва да бъдат разделени, които след това представляват действителните зависимости на оригиналния клас. Не може да се изключи, че самият оригинален клас вече не е необходим и следователно не е приложим, тъй като единствената му задача е да изпълнява задачите на своите зависимости.
Разрешаване на зависимости
Не всяка зависимост трябва да бъде разрешена чрез конфигурацията на услугата, камо ли изобщо директна зависимост за клас. Въпреки това те се вмъкват чрез конструктора чрез услуга. Бързата и лесна конфигурация на услугите означава, че промените могат да се извършват бързо, без да се налага да се сменят местата в кода, който извиква услугата. Това кани разработчик да реши всичко относно конфигурацията на услугата за засегнатата услуга.
В следващия пример класът Foo има зависимост от $ myService, която се предава в конструктора.
В примера обаче тази зависимост се използва само в метода doSomething. Ако зависимостта не е необходима от самия клас, а само от един от методите му, трябва да се обмисли дали следващият подход може да е по-добро решение:
Недостатък: Класовете, които използват Foo и неговия метод doSomething имат зависимост от $ myService.
Като втори подход трябва също да се разгледа дали задачата на метода doSomething не принадлежи към собствения си клас и трябва да бъде конфигурирана като отделна услуга. Тази нова услуга обаче не е задължително да е зависимост в класа Foo.
В този примерен код задачата на метода doSomething е напълно премахната от класа Foo и внедрена в нов клас “Qux”, който като услуга има зависимост от $ myService в предишния пример.
Разбира се, този пример е опростен, тъй като в случай на рефакторинг има и други препятствия. Когато структурата на класа се промени, промените трябва да се направят на всички места, които са използвали Foo. Промяната на структурата на класа е още по-трудна, ако е зададена от интерфейс.
Инжектиране на зависимост и услуги
Инжектирането на зависимост е общ термин, който има много различни форми на изпълнение. Решението кой от формулярите трябва да се използва в конкретен случай не може да бъде взето от контейнер за инжектиране на зависимости или локатор на услуги, а трябва да бъде решено от самия разработчик. Рамките ни предлагат функционалности, които ни помагат при разработването и трябва да намалят много досадни стъпки. Как ще се използва, все още зависи от нас.