Реализация Декоратора в Unity с Zenject

Есть интерфейс:

public interface IUpdatable 
{
    public void Awake();
    public void Start();
    public void Update();
    public void FixedUpdate();
    public void LateUpdate();
}

Данный интерфейс создан для того,что бы дергать Юнит-ишные методы у классов, не наследующихся от MonoBehaviour. У меня есть класс PlayerController, который реализует этот интерфейс.

Вот сам декоратор. Я пытаюсь централизовать все вызовы в одном месте с помощью итерфейса, но при запуске List<IUpdatable> updatable имеет Count = 0;,хотя как минимум один наследник у IUpdatable есть.

public class UpdatableDecorator : MonoBehaviour
{
    private List<IUpdatable> _updatables  = new();
    
    [Inject]
    public void Construct(List<IUpdatable> updatable)
    {
        _updatables.AddRange(updatable);
        Debug.Log(_updatables.Count);
    }
    private void Awake()
    {

        foreach (var awake in _updatables)
        {
            awake.Awake();
        }
    }
    void Start()
    {
        foreach (var start in _updatables)
        {
            start.Start();
        }
    }


    void Update()
    {
        foreach (var update in _updatables)
        {
            update.Update();
        }
    }
    private void FixedUpdate()
    {
        foreach (var fixedUpdate in _updatables)
        {
            fixedUpdate.FixedUpdate();
        }

    }

    private void LateUpdate()
    {
        foreach (var lateUpdate in _updatables)
        {
            lateUpdate.LateUpdate();
        }
    }
}

Вот так выглядит бинд в SceneInstaller:

private void BindDicorator()

{
      Container.Bind<List<IUpdatable>>().AsSingle();
      Container.BindInterfacesAndSelfTo<UpdatableDecorator>().FromComponentInHierarchy().AsSingle();
}

Не могу понять, что я делаю не так. Буду благодарен за наставление.


Ответы (1 шт):

Автор решения: Vladimir
  1. Вам не нужно вот это:

    Container.Bind<List<IUpdatable>>().AsSingle();

    Вам нужно проинсталлить только сам класс, реализующий IUpdatable:

    Container
        .BindInterfacesTo<ClassThatImplementsIUpdatable>()
        .AsSingle();
    

    Zenject умный и автоматически проинжектит все реализации IUpdatable в ваш List.


  1. Zenject гарантирует, что метод, помеченный [Inject] будет вызван до вызова метода Start, но не дает такой гарантии относительно Awake. То есть, Awake может быть вызван раньше,чем Construct. Из чего следует, что из Awake нельзя обращаться к проинжекченным объектам.

    То есть, в вашем случае, вот это

    foreach (var awake in _updatables)
    {
        awake.Awake();
    }
    

    или нужно удалить вообще, или вызывать его из метода Construct. Я бы рекомендовал вообще удалить, так как см. пункт 4.


  1. Это, в принципе, не нужно делать, поскольку в Zenject уже всё реализовано.

    Создаем класс так:

    public class MyClass : 
        IInitializable, 
        ITickable, 
        IFixedTickable,
        ILateTickable, 
        IDisposable {
        // Implement interfaces
    }
    

    Инсталлим так:

    Container
        .BindInterfacesTo<MyClass>()
        .AsSingle();
    

    Легенда такая:

    • Start -> IInitializable
    • Update -> ITickable
    • FixedUpdate -> IFixedTickable
    • LateUpdate -> ILateTickable
    • OnDestroy -> IDisposable

  1. Нетрудно заметить, что в предыдущем пункте отсутствует Awake. Однако, это не совсем так: для обычного класса аналогом Awake попросту является его конструктор. Вернее, даже наоборот, это в MonoBehaviour мы вынуждены вместо нормального конструктора использовать связку Awake и Construct. Поэтому, можно считать, что то, что вы пытаетесь сделать уже полностью реализовано в Zenject. Ну, разве что, можно создать интерфейс IUpdatable, который объединит в себе все эти отдельные интерфейсы:

    public interface IUpdatable : 
        IInitializable, 
        ITickable, 
        IFixedTickable, 
        ILateTickable, 
        IDisposable { }
    

  1. Замечание на будущее: будьте внимательны, когда начнете использовать WithId(), WhenInjectedInto() и When() в биндингах. С их помощью вы ограничите, куда Zenject будет пытаться инжектить. Поэтому, чтобы эти интефейсы работали, придется создать связанный биндинг для них, без этих ограничений.
→ Ссылка