← Назад к вопросам

Как можно заинжектить зависимости в объект созданный в Runtime?

2.0 Middle🔥 71 комментариев
#Другое

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Способы внедрения зависимостей для Runtime-объектов в Unity

Внедрение зависимостей (Dependency Injection, DI) в Unity для объектов, создаваемых во время выполнения, требует специальных подходов, поскольку такие объекты не могут быть сконфигурированы через инспектор. Рассмотрим основные стратегии и их реализацию.

1. Использование сервис-локатора или DI-контейнера

Наиболее чистый подход — создание централизованного контейнера зависимостей, который управляет всеми зависимостями в приложении.

Реализация простого DI-контейнера:

public class DIContainer : MonoBehaviour
{
    private static DIContainer _instance;
    private Dictionary<Type, object> _services = new Dictionary<Type, object>();
    
    public static DIContainer Instance 
    {
        get
        {
            if (_instance == null)
            {
                var go = new GameObject("DIContainer");
                _instance = go.AddComponent<DIContainer>();
                DontDestroyOnLoad(go);
            }
            return _instance;
        }
    }
    
    public void Register<T>(T service)
    {
        _services[typeof(T)] = service;
    }
    
    public T Resolve<T>()
    {
        return (T)_services[typeof(T)];
    }
}

Инъекция при создании объекта:

public class Enemy : MonoBehaviour
{
    private IWeapon _weapon;
    private IAudioService _audioService;
    
    public void Initialize(IWeapon weapon, IAudioService audioService)
    {
        _weapon = weapon;
        _audioService = audioService;
    }
    
    public void Attack()
    {
        _weapon.Fire();
        _audioService.PlaySound("Attack");
    }
}

// Создание с инъекцией зависимостей
var enemyPrefab = Resources.Load<GameObject>("Enemy");
var enemyObj = Instantiate(enemyPrefab);
var enemy = enemyObj.GetComponent<Enemy>();

// Получение зависимостей из контейнера и инъекция
enemy.Initialize(
    DIContainer.Instance.Resolve<IWeapon>(),
    DIContainer.Instance.Resolve<IAudioService>()
);

2. Фабричный метод с внедрением зависимостей

Создание фабрики, которая инкапсулирует логику создания объектов с правильными зависимостями.

public class EnemyFactory
{
    private IWeapon _weapon;
    private IAudioService _audioService;
    
    public EnemyFactory(IWeapon weapon, IAudioService audioService)
    {
        _weapon = weapon;
        _audioService = audioService;
    }
    
    public Enemy CreateEnemy(GameObject prefab, Vector3 position)
    {
        var enemyObj = Object.Instantiate(prefab, position, Quaternion.identity);
        var enemy = enemyObj.GetComponent<Enemy>();
        enemy.Initialize(_weapon, _audioService);
        return enemy;
    }
}

3. Использование ScriptableObject как контейнер зависимостей

ScriptableObject может служить конфигурационным файлом с зависимостями:

[CreateAssetMenu(fileName = "EnemyConfig", menuName = "Configs/EnemyConfig")]
public class EnemyConfig : ScriptableObject
{
    public GameObject Prefab;
    public IWeapon Weapon;
    public float Health;
}

public class EnemySpawner : MonoBehaviour
{
    [SerializeField] private EnemyConfig _config;
    
    public Enemy SpawnEnemy(Vector3 position)
    {
        var enemyObj = Instantiate(_config.Prefab, position, Quaternion.identity);
        var enemy = enemyObj.GetComponent<Enemy>();
        enemy.Initialize(_config.Weapon);
        return enemy;
    }
}

4. Метод Setter Injection (инъекция через свойства)

Для объектов, которые могут менять зависимости динамически:

public class NPC : MonoBehaviour
{
    private IDialogueSystem _dialogueSystem;
    
    public IDialogueSystem DialogueSystem
    {
        get => _dialogueSystem;
        set => _dialogueSystem = value ?? throw new ArgumentNullException(nameof(value));
    }
    
    public void StartConversation()
    {
        _dialogueSystem?.ShowDialogue("Hello!");
    }
}

// Использование
var npc = Instantiate(npcPrefab).GetComponent<NPC>();
npc.DialogueSystem = DIContainer.Instance.Resolve<IDialogueSystem>();

5. Комбинированный подход через Zenject/Extenject

Для промышленной разработки рекомендую использовать проверенные DI-фреймворки:

// Установка через Package Manager: Extenject (Zenject)
public class EnemyInstaller : MonoInstaller
{
    [SerializeField] private GameObject _enemyPrefab;
    
    public override void InstallBindings()
    {
        Container.Bind<IWeapon>().To<LaserWeapon>().AsSingle();
        Container.Bind<IAudioService>().To<AudioManager>().AsSingle();
        
        Container.BindFactory<Enemy, Enemy.Factory>()
            .FromComponentInNewPrefab(_enemyPrefab)
            .WithGameObjectName("Enemy")
            .UnderTransformGroup("Enemies");
    }
}

public class EnemySpawner : MonoBehaviour
{
    [Inject] private Enemy.Factory _enemyFactory;
    
    public void Spawn()
    {
        var enemy = _enemyFactory.Create();
        // Зависимости уже внедрены автоматически
    }
}

Ключевые рекомендации:

  • Используйте интерфейсы для зависимостей вместо конкретных классов
  • Внедряйте зависимости сразу после Instantiate, до вызова Awake/Start
  • Рассмотрите использование событий (UnityEvent/C# events) для слабой связанности
  • Для сложных проектов выбирайте Zenject/Extenject или VContainer
  • Тестируемость — правильная DI значительно упрощает unit-тестирование

Паттерны для избежания антипаттернов:

  • Избегайте Service Locator как антипаттерн — используйте его осознанно
  • Не передавайте MonoBehaviour-зависимости через конструктор
  • Используйте Lazy Initialization для тяжелых зависимостей

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