Обзор DI-фреймворков для Unity

Обзор DI-фреймворков для Unity

Недавно открыл для себя новый DI-фреймворк — RefleX, который, как оказалось, уже давно набирает популярность. Он является аналогом известных многим Zenject/Extenject и VContainer и открыто себя им противопоставляет. Стоит ли этот фреймворк внимания, что лучше выбрать, какие есть альтернативы — об этом расскажу далее, опираясь на свой опыт.

Zenject

Обзор DI-фреймворков для Unity

Золотая классика DI для Unity. Сам Zenject перестал поддерживаться, поэтому сейчас, говоря Zenject, часто имеют в виду его форк — Extenject, который поддерживается сообществом.

Принято "пинать" Zenject за его тяжеловесность и считать, что все проблемы и лаги в приложении из-за него. Какое-то время назад это было популярной темой для докладов и споров в интернетах.

Несомненно, в лабораторных условиях, по бенчмаркам, Zenject далеко не чемпион. И в силу своего возраста, и в силу своей тяжеловесности. Порой это действительно становится проблемой. Но в реальном использовании в большинстве случаев проблемы приносит сам разработчик, а не инструмент:

  • Объёмные, Context'ы, а то и глобальные, с большим кол-ом зависимостей;
  • Логика в конструкторах, которая вызывается лениво и неконтролируемо;
  • Отсутствие фазы инициализации, отдельной и управляемой;
  • Неконтролируемое инстанциирование через какой-нибудь FromComponentInNewPrefab;
  • Инжет в GameObject'ы и сцены с большим кол-ом компонентов и дочерних объектов.

И множество других нюансов, которые допускают разработчики прежде чем искать замену Zenject в надежде, что новый инструмент автоматически решит их проблемы.

В дополнение к этому Zenject по умолчанию многое пытаётся автоматизировать и сделать сам, что менее эффективно, чем явный контроль со стороны разработчика.

На мой взгляд, основное достоинство и основная проблема Zenject — это его монструозность. Он слишком много умеет и даёт слишком много свободы. Он буквально позволяет сделать всё, что придёт в голову. Ну или, как минимум, не мешает этому.

Достаточно взглянуть на его документацию — она гигантская. В частности, из-за этого немногие её осиливают и знают о всех возможностях, которые предоставляет Zenject.

Например, кто знаком с Reflection Baking, который позволяет ускорить Zenject на IL2CPP и сразу сделать многие бенчмарки и доклады в сети неактуальными?

А там ещё и сигналы, и фабрики, и пулы, и декораторы, и чего только нет.

Таким большим "комбайном", несмотря на коварную удобность, очень сложно управлять. Для неопытного разработчика открывается массу соблазнов, которые неминуемо "выстрелят" в самый неожиданный момент и в самом неожиданном месте.

Но в опытных руках это рабочий, распространённый, проверенный, надёжный, удобный, многофункциональный "засахаренный по самые не балуйся" инструмент. К тому же он отлично работает и за пределами Unity, что позволяет использовать его в SharedLogic (статья на Хабре).

VContainer

Обзор DI-фреймворков для Unity

VContainer — более свежая, легковесная и производительная альтернатива Zenject, которая предоставляет необходимый набор возможностей для реализации именно DI. Здесь намного меньше "сахара" и нет ничего лишнего.

Им можно полностью заменить Zenject. Что-то придётся дописать самостоятельно, где-то обновить синтаксис и немного пересмотреть свои подходы, но в контексте DI они взаимозаменяемы.

Документация небольшая, простая и очень понятная. Есть даже раздел, посвящённый сравнению и переходу с Zenject.

Наделать здесь нелепых ошибок по неопытности намного сложнее. Сам фреймворк чуточку позамороченнее, поэтому сделать какую-то "отсебятину" сходу не получится — фреймворк будет препятствовать, но и не блокировать эту возможность полностью.

Также здесь меньше уровень "автоматизации", и из-за этого вынужденно вырастает уровень контроля. Что положительно сказывается на предсказуемости, стабильности и производительности.

С некоторым "подпиливанием" он тоже выносится за пределы Unity и подключается к SharedLogic.

Есть поддержка UniTask, UniRx, MessagePipe, Unity Entities.

Для пущей оптимизации можно подключить Source Generators и асинхронную или даже параллельную сборку Di-контейнеров.

Не так распространён, как Zenject, но уверенно идёт следующим по популярности среди сторонних решений. Удобный, простой, надёжный. И, конечно, более производительный и компактный.

RefleX

Обзор DI-фреймворков для Unity

При всех громких заявлениях о своём превосходстве, точно не является "убийцей зенжектов". Однако это вполне жизнеспособная альтернатива со своими фишками и синтаксисом, который кому-то может понравиться больше.

Помимо уровня оптимизации можно отметить симпатичное окно отладки, похожее на аналогичное в VContainer. Я подобным пользуюсь редко, поэтому наличие такого инструмента — определённый плюс, но не критически важный.

Что мне не понравилось:

  1. Упрощён по возможностям, возможно в угоду производительности: мало Fluent-дизайна, указание типа только через typeof без дженериков, отсутствие возможности регистрации с дополнительными или переопределёнными параметрами.
  2. Нет поддержки Open Generics (пример из VContainer). Не топ-функция по важности, но частенько встречается в DI-решениях и на больших проектах помогает сократить кол-во бойлерплейта.
  3. Неочевидный способ работы Scope. Он просто ищет Installer'ы через GetComponentInChildren в Runtime и даже не отрисовывает этого в инспекторе. Если бы оно просто сериализовалось до запуска, это было бы более наглядно, контролируемо и "дешевле" (не нужно ничего искать).
  4. Быстрый, но не самый быстрый. Есть опережающие конкуренты среди SparseInject, Pure.DI и ManualDI.
Пример Scope и Installer
Пример Scope и Installer

В остальном это выглядит как нечто среднее между Zenject и VContainer. Возможностей меньше, чем у обоих, но и в производительности, если верить опубликованным бенчмаркам, он их обгоняет. Вполне пригодное для использования решение. Стоит ли тащить его на крупный продакшн — ответить затрудняюсь. Но в простых или чуть менее коммерческих проектах пробовать можно.

Сравнительная таблица

Обзор DI-фреймворков для Unity

Другие альтернативы

Помимо упомянутых ранее SparseInject, Pure.DI и ManualDI найти другие решения, стоящие внимания, непросто. Если вбить в поиск DI на AssetStore или Github преимущественно будут попадаться варианты типа UniDi, Adic или Init(args).

Можно ещё упомянуть молодой Tarject, который очень похож на Zenject.

Некоторые ECS-фреймворки имеют свой DI, например EcsLite. Но часто не весь проект построен по заветам ECS.

Можно свободно использовать решения из NuGet, ориентированные для dotnet. Но:

  • Вручную потребуется сделать обвязки для Unity-контекста.
  • Могут быть в работе ограничения на определённых платформах.
  • Может быть слишком большое кол-во зависимостей, которые сложно подключить к Unity.

Поэтому это возможно, но не очень оправданно. Подойдёт разве что, если есть определённые предпочтения или привычки, которым не хочется изменять.

Что выбрать

Для коммерческой разработки, где цена каждой ошибки высока, лучше брать стабильные, проверенные и наиболее распространённые решения: Zenject и VContainer. Или понятное контролируемое in-house решение.

Написать свой фреймворк под проект — тоже вариант. Это не так сложно делается.

  • Минус: чем больше проект, тем больше этот фреймворк требует доработок. Насколько он в итоге окажется лучше готового решения, проверенного тысячами разработчиков, — вопрос. И это не считая времени, затраченного на реализацию и поддержку.
  • Плюс: если размер билда критически важен и каждый мегабайт на счету, то собственное решение без лишних зависимостей, содержащее только нужный код, подходит как нельзя лучше. Но здесь можно обойтись и без фреймворка или даже без DI вообще.

Для инди и пет-проектов условия проще: стоит брать то, с чем удобнее, понятнее и интереснее работать и что умеет решать поставленные задачи. Если говорить про рассмотренные варианты выше, то все они отлично подойдут и на 99% закроют все потребности по внедрению зависимостей.

3
1
4 комментария