Pull to refresh

Comments 17

Если кому интересно, с месяц назад делал for fun свой простой DIC по образу и подобию симфониевского. Вроде, неплохо получился. github.com/NeonXP/DIC
Использование DI позволяет не задумываться над тем, в каком контексте будет использоваться ваш модуль. Дает возможность переносить отдельный класс в другой проект без набора (часто иерархического) зависимостей. При использовании аннотаций вам не придётся заниматься явным созданием объектов и явной передачей параметров и сервисов в объект. И, конечно, такой класс в разы проще поддается тестированию, нежели завязанный на статические методы или явно создающий экземпляры класса, вместо использования фабрики.


При чём тут DI?
Каким образом вы избавитесь от зависимостей?
Что мешает даже при использвании DI засунуть в класс статики?
Почему класс созданный через фабрику легче подается тестированию?
Каким образом вы избавитесь от зависимостей?

Подразумевается зависимость от конкретных классов. При описанном подходе во-первых возможно изменить используемый класс через настройки сервис локатора, во вторых явно задать реализацию сервиса (при наличии set- метода).

Что мешает даже при использвании DI засунуть в класс статики?

Использование DI ничему не мешает, оно позволяет этого не делать.

Почему класс созданный через фабрику легче подается тестированию?

Всё-таки фабрика создает экземпляры, а не классы. В приведенном абзаце подразумевается удобство тестирование именно порождающего объекты класса, а не порождаемого.

Т.е. для тестирования класса, в котором имеется подобный код:
$m = new Model();
$m->setValue('a', 1);
return $m;

Необходимо существование класса «Model». В то же время убедиться в том, что тестирумый класс действительно выполнил setValue() не всегда возможно, для этого необходимо, чтобы класс Model уже работал.

При использовании фабрики, код будет таким:
$m = $this->modelFactory()->create();
$m->setValue('a', 1);
return $m;

И в тесте, подменяя фабрику, мы можем через метод modelFactory()->create() вернуть mock объект, для которого проверим однократный вызов setValue() с ожидаемыми параметрами.
Подразумевается зависимость от конкретных классов. При описанном подходе во-первых возможно изменить используемый класс через настройки сервис локатора, во вторых явно задать реализацию сервиса (при наличии set- метода).


Когда класс не сферический в вакууме а зависит допустим от модели данных, от библиотек (тех же симфони компонентов, доктрины, etc), то внедряй не внедряй, а иерархию зависимостей потянешь. Если у вас где-то используется entity manager — перенести класс в проект без доктрины уже нельзя.

— Почему класс созданный через фабрику легче подается тестированию?

— И в тесте, подменяя фабрику, мы можем через метод modelFactory()->create() вернуть mock объект, для которого проверим однократный вызов setValue() с ожидаемыми параметрами.


Согласен, речь идет о физической невозможности написать юнит тест. Хотя имхо, фабрика — излишняя абстракция, которая в первую очередь путает разработчика. Что выпрыгнет из черного ящика? А кто его знает. Нужно полезть в код фабрики, посмотреть. Нужно полезть в конфигурацию сервис локатора, посмотреть. Ой, не ту конфигурацию посмотрел. Не то вылезло.

Если реально стоит вопрос тестирования, лучше заюзайте AspectMock.

DI реально усложняет код. Если не планируется замена сервисов, а это случится только если вы пишите расширяемую либу, или фреймворк, то в большинстве случаев DI не нужно. Что точно — не нужен повесместно. Если внядряете DI только ради «тестирования», то нет смысла, продакшн код подгонять под ограничения средств тестирования.
Если у вас где-то используется entity manager — перенести класс в проект без доктрины уже нельзя.

Почему же, достаточно определить свой entity manager. В симфони весьма гибкая система конфигов и сервис doctrine.entity_manager вполне возможно заменить. Конечно, если в целевом объекте явно не указан класс менеджера сущностей, а подставляемый сервис имеет соответствующий интерфейс.

Если реально стоит вопрос тестирования, лучше заюзайте AspectMock.

Чем же непереносимый код лучше подхода с использованием многократно описанных паттернов?

Что выпрыгнет из черного ящика?

Абсолютно не важно, главное чтобы оно имело соответствующий интерфейс.

DI реально усложняет код.

FooBar::getSome();
$this->fooBar->getSome();
Возможно, это всё-таки дело привычки и конкретной реализации.

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

То позже возникают проблемы в поддержке проекта.
Почему же, достаточно определить свой entity manager.

Ага, можно имплементировать всё методы ентити менеджера, а в итоге написать свою доктрину. Вот только зачем?

Я к тому, что не бывает универсального кода, который учтет абсолютно всё. Пусть использование DI помогает вводить некоторую абстракцию в код, это совершенно не значит, что проектируя с DI мы свободно сможем заменять любые компоненты. Ибо интерфейс ентити менеджера доктрины имплементирует только энтити менеджер доктрины. Если мы меняем ORM, нам недостаточно заменить один сервис на другой, нам нужно переписать большую часть кода.
Если переносимый сервис пользуется ограниченным набором методов, допустим одним лишь find(), вполне возможно не подключать в проект доктрину, а создать адаптер к имеющейся субд.
Если вам нужна большая часть функционала доктрины, скорее всего вам нужна именно доктрина.

это совершенно не значит, что проектируя с DI мы свободно сможем заменять любые компоненты.

На самом деле, означает. Целесообразность замены, конечно, зависит от конкретного случая.
$m = $this->modelFactory()->create();
$m->setValue('a', 1);
return $m;

В данном куске кода нужно тестировать именно create. Так что я не понимаю каким образом замена new на create облегчает вам тестирование.
А MockObject нужен не для того что бы тестировать, а для того что бы его подсунуть тестируемому классу.

Использование DI ничему не мешает, оно позволяет этого не делать.

Не использование DI тоже позволяет не использовать статики.

Подразумевается зависимость от конкретных классов. При описанном подходе во-первых возможно изменить используемый класс через настройки сервис локатора, во вторых явно задать реализацию сервиса (при наличии set- метода).

DI ради DI? Вы описали выгоды использования DI для DI.
Зависимость от конкретных классов решается введением интерфейсов, при чём тут DI, я опять не вижу.
В данном куске кода нужно тестировать именно create.

Метод create() протестирован в тесте фабрики. Метод __construct() тестируется в тесте класса, в котором он объявлен.
В данном куске кода проверяется работа тестируемого сервиса, а не используемых в нем.

Вы описали выгоды использования DI для DI.

Мне кажется, сейчас вы путаете частный случай DI и сам принцип IoC. Вопрос сводится к обоснованию самой инверсии управления?
Всё дело в заключении.
Вы описали приемущества использования DI, хотя DI к тем тезисам не имеет никакого отношения.
И я не путаю DI и IoC. Вы явно указали, что настраиваете(конфигурируете) контейнер.
Метод create() протестирован в тесте фабрики. Метод __construct() тестируется в тесте класса, в котором он объявлен.
В данном куске кода проверяется работа тестируемого сервиса, а не используемых в нем.

В чём тогда суть теста?
Если create протестирован, то зачем вызывать у какого то MockObject методы и тестировать их?
Как бы, я наводящие вопросы вам задаю, что бы вы ошибку поняли. Ну да ладно.
Причём тут DI?

По ссылке из википедии:
Используя же внедрение зависимости, объект просто предоставляет свойство, которое в состоянии хранить ссылку на нужный тип сервиса; и когда объект создается, ссылка на реализацию нужного типа сервиса автоматически вставляется в это свойство (поле), используя средства среды.
Да, реализация примитивна, здесь вышеназванным средством среды выступает SimleDi\ServiceLocator, который занимается и созданием объектов и внедрением зависимостей, в одноименном паттерне (service locator) такого не предусмотрено. Да, это не его задача.
«500+ классов моделей, 300+ классов контроллеров»
А это что, маленький проект?
Прошу прощения за сарказм в первом предложении поста, я действительно считаю этот проект большим и достойным единой продуманной концепции работы с объектами.
Зачем аннотации, если есть type hinting?
Мне вот это решение нравится: github.com/Jasrags/Dice
Минимум телодвижений, все работает без дополнительного вмешательства
Sign up to leave a comment.

Articles