0

I'm working with the Dependency Injection Pattern in Unity3D (an engine which uses MonoBehavior, a class that doesn't have a constructor, as the base class for all of its game components), and I ended up building my own solution for it. I don't think that, aside from having to write some boilerplate, it's an anti pattern (specially with the few solutions around the system).

I would like to hears cons and alternative solution to my system as I'm still refining it.

Take into consideration that I'm building this system to be easy to use, scalable (up to a certain level, it doesn't have to be a solution to every problem) and it must work with MonoBehaviours.

My system works the following way:

  1. You have a single instance of an interface which contains all the dependencies for the whole applicatio. Let's call this IEverything
  2. Each class that needs DI will either inherit from a base class (DependencyInjectionComponent) or use an interface and apply the subscription by themselves.
  3. On the Awake method all the classes will subscribe/inform the injector and will have the IEverything instance injected (same instance for every class).
  4. The class must manually extract the dependencies it needs.

To represent it with code it would be something like this:

public interface IEverything
{
    ILogger Logger { get; }
    IAnalytics Analytics { get; }
    IOtherServices OtherServices { get; }
}

public class MyMonobehaviourWhichDoesNotHaveAConstructor : DependencyInjectionComponent
{
    private ILogger logger;
    
    // abstract method in the base class
    public void Inject(IEverything everything)
    {
        logger = everything.Logger;
    }
}

As you can see, the implementation only needs one object, so it disregards the rest.

Now, ideally, if I could use constructors I would make it like this:

public class NormalClass
{
    private readonly ILogger logger;

    public NormalClass(ILogger logger)
    {
        this.logger = logger;
    }
}

But I'm afraid I can't.

Another option is to use a library like Zenject which allows to do something like this:

public class NormalMonoBehaviour : MonoBehaviour
{
    [InjectableTag] private ILogger logger;
}

The problem with Zenject is that it can be slow because, with several components, it takes some time to inject the dependencies. "Some time" means that they won't be available in the Awake method but they will be available on the Start.

Is there any way I could improve my system or any big issue it has?

From what I analysed, the two biggest problems I found are:

  • It requires inheritance or to use an interface
  • It injects a big object (although, by sharing the reference there shouldn't be any memory problem)
9
  • 2
    Does this answer your question? Why is global state not considered as an example of dependency injection? Commented Aug 6, 2020 at 3:25
  • Also see Is it bad design to have a class represent your entire program?
    – mtj
    Commented Aug 6, 2020 at 5:39
  • I saw both of them but it seems they handle two wrong implementations, the global state one is more about a singleton pattern (which I'm not using here) and the second one is slightly different to my implementation (I don't store the main object, but the sub objects in it)
    – Jallrich
    Commented Aug 6, 2020 at 9:18
  • 1
    @JavierBullrich It does not matter which components you extract and store from IEverything. The point is, that you pass IEverything, and therefore don't have any visibility of the real dependency from the outside. Therefore, it is a service-locator (anti-) pattern, and all things said about service locators in the links above applies to your question.
    – mtj
    Commented Aug 7, 2020 at 5:20
  • 1
    @GregBurghardt it is exactly as you say, Unity instantiate them so you have to find a different approach to it.
    – Jallrich
    Commented Aug 8, 2020 at 8:46

1 Answer 1

2

What you are describing is the service locator pattern. Most times it is considered an anti-pattern, however given that Unity instantiates the components, and likely requires a constructor with certain arguments, a service locator is about your only choice.

The most important part of your solution is naming the method that receives the service locator. Make the names obvious.

First, I would rename IEverything to IMonoBehaviorServiceLocator. It might be a long name, but it is clear. You are utilizing a known anti-pattern for certain components. You might not want to use this service locator everywhere, but in this one area of the application it is fine — it is certainly better than having a dozen or more classes new-ing up the same objects over and over.

The Inject method might need renaming too. Something like ReceiveDependencies or InitializeDependencies might be a better name:

public void InitializeDependencies(IMonoBehaviorServiceLocator serviceLocator)
1
  • Regarding the naming convention, I actually have better defined names and the method itself also has a well defined name, but thanks for your answer, I'm happy that you helped me identify what pattern I am using and the problems and lack of solutions with it.
    – Jallrich
    Commented Aug 14, 2020 at 12:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.