#архитектура #dependency_injection #di #zenject
Приветствую, друзья 👋
Сегодня говорим про Dependency Injection (DI) и DI фреймворки.
Статью дробим на 3 части:
1. Рассмотрим самый популярный фреймворк Zenject, обозначим недостатки.
2. Выделим достоинства Zenject и дадим общие рекомендации по выбору фреймворка.
3. Проиллюстрирую ряд обозначенных проблем примерами кода.
Итак, поехали 🚀
Самый распространенный и популярный DI фреймворк - Zenject, существует с 2014 года.
Zenject используется как независимыми разработчиками, так и крупными игровыми студиями, среди которых: Square Enix, Electronic Arts, Ubisoft, Zenimax Online Studios и многие другие.
Zenject - будет основной линией данной статьи, на нем будем приводить примеры.
Одной из критик Zenject является высокий уровень абстракции и высокий порог входа для новичков.
В процессе работы с Zenject я выделил ряд слабых мест, которые стоит учитывать при рассмотрении данного фреймворка:
1. отсутствие полного контроля над жизненным циклом объектов.
Zenject основывается на автоматическом внедрении зависимостей и может привести к утечкам памяти при некорректном уничтожении объектов
2. проблемы с настройкой очередностей в MonoInstaller Zenject контекста.
Zenject использует собственный механизм внедрения зависимостей и контроля жизненного цикла объектов, который может конфликтовать с механизмами Unity
3. усложнение настройки зависимостей и снижении производительности из-за создания большого количества объектов и вызова методов на каждый из них (особенно в коде с большим количеством зависимостей)
4. настройка проекта. Настройка складывается из двух частей: через GUI Unity и через код. Настройка через GUI опасна, так как может повредить код, что не всегда очевидно даже для опытных разработчиков. Исправление занимает много времени. Для новичков такая настройка особенно сложна и затруднительна в освоении
5. неявное внедрение зависимости через атрибут [Inject]
6. использование фабрик, предоставляемых Zenject, для создания экземпляров MonoBehaviour.
Подход не всегда оптимален, так как фабрики, предоставляемые Zenject, ограничены в функциональности и не могут обеспечить полную гибкость, доступную пользовательским решениям.
Недостатки фабрик, предоставляемых Zenject:
- невозможность создания нескольких экземпляров одного и того же компонента с различными параметрами
- ограниченная возможность создания экземпляров, которые требуют сложной логики инициализации
- ограничения на наследование MonoBehaviour
- ограничения, связанные с созданием экземпляров внутри сцены
7. усложнение процесса обновления Unity до новых версий из-за потенциальных несовместимостей с новыми API
8. портирование Zenject проекта на другие фреймворки.
Продолжение 👇
Приветствую, друзья 👋
Сегодня говорим про Dependency Injection (DI) и DI фреймворки.
Статью дробим на 3 части:
1. Рассмотрим самый популярный фреймворк Zenject, обозначим недостатки.
2. Выделим достоинства Zenject и дадим общие рекомендации по выбору фреймворка.
3. Проиллюстрирую ряд обозначенных проблем примерами кода.
Итак, поехали 🚀
Самый распространенный и популярный DI фреймворк - Zenject, существует с 2014 года.
Zenject используется как независимыми разработчиками, так и крупными игровыми студиями, среди которых: Square Enix, Electronic Arts, Ubisoft, Zenimax Online Studios и многие другие.
Zenject - будет основной линией данной статьи, на нем будем приводить примеры.
Одной из критик Zenject является высокий уровень абстракции и высокий порог входа для новичков.
В процессе работы с Zenject я выделил ряд слабых мест, которые стоит учитывать при рассмотрении данного фреймворка:
1. отсутствие полного контроля над жизненным циклом объектов.
Zenject основывается на автоматическом внедрении зависимостей и может привести к утечкам памяти при некорректном уничтожении объектов
2. проблемы с настройкой очередностей в MonoInstaller Zenject контекста.
Zenject использует собственный механизм внедрения зависимостей и контроля жизненного цикла объектов, который может конфликтовать с механизмами Unity
3. усложнение настройки зависимостей и снижении производительности из-за создания большого количества объектов и вызова методов на каждый из них (особенно в коде с большим количеством зависимостей)
4. настройка проекта. Настройка складывается из двух частей: через GUI Unity и через код. Настройка через GUI опасна, так как может повредить код, что не всегда очевидно даже для опытных разработчиков. Исправление занимает много времени. Для новичков такая настройка особенно сложна и затруднительна в освоении
5. неявное внедрение зависимости через атрибут [Inject]
6. использование фабрик, предоставляемых Zenject, для создания экземпляров MonoBehaviour.
Подход не всегда оптимален, так как фабрики, предоставляемые Zenject, ограничены в функциональности и не могут обеспечить полную гибкость, доступную пользовательским решениям.
Недостатки фабрик, предоставляемых Zenject:
- невозможность создания нескольких экземпляров одного и того же компонента с различными параметрами
- ограниченная возможность создания экземпляров, которые требуют сложной логики инициализации
- ограничения на наследование MonoBehaviour
- ограничения, связанные с созданием экземпляров внутри сцены
7. усложнение процесса обновления Unity до новых версий из-за потенциальных несовместимостей с новыми API
8. портирование Zenject проекта на другие фреймворки.
Продолжение 👇
#архитектура #dependency_injection #di #zenject
✅ Преимущества Zenject:
1. инверсия управления (IoC) и внедрение зависимостей (DI) помогают ослабить зацепление классов и улучшить тестируемость кода
2. поддержка скоупов (при условии их корректного использования) позволяет управлять жизненным циклом объектов и оптимизировать использование ресурсов
3. интеграция с Unity позволяет использовать DI в контексте игровых объектов и компонентов
4. использование интерфейсов в Zenject позволяет значительно повысить уровень абстракции в проекте.
Разделить проект на слои абстракции, каждый из которых будет содержать свои интерфейсы и реализации. Изменения в одном слое не будут затрагивать другие слои.
Кроме того, использование интерфейсов позволяет легко заменять реализации во время тестирования и создавать мок-объекты для модульного тестирования.
Zenject оптимален для слаженных и опытных командам с малой текучкой кадров и высоким уровнем компетенции.
Общие советы при выборе DI фреймворка:
- опирайтесь на потребности проекта и компетенцию команды разработчиков
- помните, использование DI фреймворков не всегда обязательно, зависит от целей проекта и его масштаба. Например, разработка прототипа не всегда требует использования DI фреймворка, так как направлена на быстрое создание простой модели продукта для проверки гипотез и его концепции
- основывайтесь на балансе между функциональностью фреймворка и сложностью его использования. Учитывайте особенности и преимущества, чтобы выбрать оптимальный для текущей задачи
- учитывайте наличие документации и наличие сообщества у выбранного фреймворка
- протестируйте выбранный DI фреймворк перед его использованием в реальном проекте.
Использование DI фреймворков может упростить разработку, облегчить поддержку и улучшить переносимость кода между разными проектами.
Жду вопросы в комментариях!
В следующей публикации приведу примеры-иллюстрации проблемных мест Zenject👇
✅ Преимущества Zenject:
1. инверсия управления (IoC) и внедрение зависимостей (DI) помогают ослабить зацепление классов и улучшить тестируемость кода
2. поддержка скоупов (при условии их корректного использования) позволяет управлять жизненным циклом объектов и оптимизировать использование ресурсов
3. интеграция с Unity позволяет использовать DI в контексте игровых объектов и компонентов
4. использование интерфейсов в Zenject позволяет значительно повысить уровень абстракции в проекте.
Разделить проект на слои абстракции, каждый из которых будет содержать свои интерфейсы и реализации. Изменения в одном слое не будут затрагивать другие слои.
Кроме того, использование интерфейсов позволяет легко заменять реализации во время тестирования и создавать мок-объекты для модульного тестирования.
Zenject оптимален для слаженных и опытных командам с малой текучкой кадров и высоким уровнем компетенции.
Общие советы при выборе DI фреймворка:
- опирайтесь на потребности проекта и компетенцию команды разработчиков
- помните, использование DI фреймворков не всегда обязательно, зависит от целей проекта и его масштаба. Например, разработка прототипа не всегда требует использования DI фреймворка, так как направлена на быстрое создание простой модели продукта для проверки гипотез и его концепции
- основывайтесь на балансе между функциональностью фреймворка и сложностью его использования. Учитывайте особенности и преимущества, чтобы выбрать оптимальный для текущей задачи
- учитывайте наличие документации и наличие сообщества у выбранного фреймворка
- протестируйте выбранный DI фреймворк перед его использованием в реальном проекте.
Использование DI фреймворков может упростить разработку, облегчить поддержку и улучшить переносимость кода между разными проектами.
Жду вопросы в комментариях!
В следующей публикации приведу примеры-иллюстрации проблемных мест Zenject👇
#архитектура #dependency_injection #di #zenject #код
Рассмотрим пример кода проблемных мест на базе Zenject обозначенных в предыдущей публикации.
Пример 1. Проблемы с производительностью
✅Одно из возможных решений проблемы производительности - использование объекта SomeDependency в виде синглтона, созданного во время запуска приложения, и использование его в качестве зависимости в SomeClass. Подход позволит избежать повторного создания объекта и улучшить производительность приложения.
Пример 2. Проблемы с циклической зависимостью
SomeDependency и SomeOtherDependency зациклены друг на друга, (т.е. SomeDependency требуется SomeOtherDependency и наоборот), при запуске приложения будет выброшено исключение об ошибке внедрения зависимостей.
Пример 3. Неявное внедрение зависимости через атрибут [Inject]
В результате, кодовая база может раздуться, так как для каждого интерфейса требуется создать реализацию, даже если это тривиальная реализация или если интерфейс используется только для DI.
Проблемы использования атрибута [Inject]:
1. Зависимости становятся скрытыми и сложными в управлении. Передаются неявно, что затрудняет понимание, какие зависимости в каких частях кода, и как их переиспользовать.
2. Ошибки времени выполнения.
В случае, если некоторые зависимости не были правильно настроены
3. Низкая гибкость. Сложно управлять жизненным циклом зависимостей, некоторые операции, например, уничтожение или замена зависимостей, могут быть затруднены.
4. Снижение производительности. В некоторых случаях требуются дополнительные операции по поиску и инициализации зависимостей.
Благодарим за внимание!
Хороших выходных 😉
Рассмотрим пример кода проблемных мест на базе Zenject обозначенных в предыдущей публикации.
Пример 1. Проблемы с производительностью
public class SomeClass
{
private readonly SomeDependency _dependency;
public SomeClass(SomeDependency dependency)
{
_dependency = dependency;
}
public void DoSomething()
{
// use _dependency
}
}
public class SomeDependency
{
public SomeDependency()
{
// do something expensive
}
}
public class SomeInstaller : MonoInstaller
{
public override void InstallBindings()
{
Container.Bind<SomeClass>().AsSingle().NonLazy();
Container.Bind<SomeDependency>().AsSingle().NonLazy();
}
}
В рассмотренном примере проблема с производительностью возникает при создании объектов с зависимостями во время выполнения программы. Конструирование объекта SomeDependency в SomeClass является дорогостоящей операцией, особенно если объект создается многократно в рамках работы приложения. ✅Одно из возможных решений проблемы производительности - использование объекта SomeDependency в виде синглтона, созданного во время запуска приложения, и использование его в качестве зависимости в SomeClass. Подход позволит избежать повторного создания объекта и улучшить производительность приложения.
Пример 2. Проблемы с циклической зависимостью
public class SomeClass : MonoBehaviour
{
[Inject] private SomeDependency _dependency;
private void Start()
{
_dependency.DoSomething();
}
}
public class SomeDependency
{
private readonly SomeOtherDependency _otherDependency;
public SomeDependency(SomeOtherDependency otherDependency)
{
_otherDependency = otherDependency;
}
public void DoSomething()
{
// use _otherDependency
}
}
public class SomeOtherDependency
{
public SomeOtherDependency(SomeDependency someDependency)
{
someDependency.DoSomething();
}
}
public class SomeInstaller : MonoInstaller
{
public override void InstallBindings()
{
Container.Bind<SomeDependency>().AsSingle();
Container.Bind<SomeOtherDependency>().AsSingle();
}
}
В примере наблюдаем проблему с циклической зависимостью между SomeDependency и SomeOtherDependency. SomeDependency и SomeOtherDependency зациклены друг на друга, (т.е. SomeDependency требуется SomeOtherDependency и наоборот), при запуске приложения будет выброшено исключение об ошибке внедрения зависимостей.
Пример 3. Неявное внедрение зависимости через атрибут [Inject]
public class MyComponent : MonoBehaviour
{
private IMyDependency _myDependency;
public void DoSomething()
{
_myDependency.DoStuff();
}
}
И интерфейс, который определяет эту зависимость:
public interface IMyDependency
{
void DoStuff();
}
То, чтобы внедрить зависимость в MyComponent, нужно добавить атрибут [Inject] к полю _myDependency:
public class MyComponent : MonoBehaviour
{
[Inject]
private IMyDependency _myDependency;
public void DoSomething()
{
_myDependency.DoStuff();
}
}
При использовании интерфейса IMyDependency необходимо создать и его реализацию. В результате, кодовая база может раздуться, так как для каждого интерфейса требуется создать реализацию, даже если это тривиальная реализация или если интерфейс используется только для DI.
Проблемы использования атрибута [Inject]:
1. Зависимости становятся скрытыми и сложными в управлении. Передаются неявно, что затрудняет понимание, какие зависимости в каких частях кода, и как их переиспользовать.
2. Ошибки времени выполнения.
В случае, если некоторые зависимости не были правильно настроены
3. Низкая гибкость. Сложно управлять жизненным циклом зависимостей, некоторые операции, например, уничтожение или замена зависимостей, могут быть затруднены.
4. Снижение производительности. В некоторых случаях требуются дополнительные операции по поиску и инициализации зависимостей.
Благодарим за внимание!
Хороших выходных 😉