Dependency Injection for Unity: Zenject Framework
In the previous post, I have exposed the problems regarding the Singletons and arguably proposed the Service Locator pattern as a solution. It was quite easy to see the advantages of it. However, the usage still gives a dirty feeling. Moreover, it hides the complexity. If we don’t see the source code, the dependencies of the classes are not really explicit. At that point, Dependency Injection takes its part.
As a developer coming from the enterprise application’s world, I am not allowed to use the ‘new’ keyword. We are able to do this successfully, thanks to a Dependency Injection framework. So, I will give a basic explanation of the pattern and its advantages. In addition to that, I will introduce you to a nicely designed dependency injection framework – Zenject – written for the Unity game engine.
What the heck is Dependency Injection?
Let’s start with the idea behind Dependency Injection. We have a design principle called Inversion of Control (IoC).
Inversion of Control (IoC)
As it is clear from the name, the principle is inverting the control. Here the control refers to the flow of an application or object creation. I am not going to touch the application flow. However, if you are interested, you can find examples of it easily. My focus will be on creating dependent objects inside the class.
public class ScoreManager { private ScoreBoard _scoreBoard; public ScoreManager() { _scoreBoard = new ScoreBoard(); } }
What you see above is the pure dependency of ScoreManager to the ScoreBoard. You can ask about why this is actually bad. Well, this is a bad practice, because ScoreBoard can change silently. As a result, this change might have an impact on the ScoreManager. Besides, imagine that ScoreBoard is used in other classes as well. After that, more modification would require all of them.
public class ScoreManager { private IScoreBoard _scoreBoard; public ScoreManager(IScoreBoard iScoreBoard) { _scoreBoard = iScoreBoard; } }
In the version above, there is no new() keyword. In addition to that, ScoreManager depends on the abstraction instead of the concrete implementation. It doesn’t even care about its implementation anymore. It just gets it from outside. As you can clearly see we have inverted the control of creating objects from ScoreManager to a higher class.
How exactly Dependency Injection is related to the IoC?
I am sure that you already got the idea. So, Dependency Injection is nothing but an application of IoC. All the dependencies are created in a container. After that, an injector injects those dependencies where they are needed. Today, we have really powerful Dependency Injection frameworks. Before giving an example, I want to visualize what we achieve here.
In the figure above you see the traditional way. Imagine this dependency graph on a game project that has 200 classes. Almost every change in the classes would have a great impact.
Even from the visual explanation, it looks more clear. Dependency Injection tool contains all the instances. It distributes them to the classes which are in need. You can write your own injector, but I would rather not. Because there are great free tools out there such as Zenject.
Zenject DI Framework
Zenject is a Dependency Injection framework mainly written for Unity. You can use it in your c# projects as well. It is a free and open-source. Most of the platforms are supported. It provides a variety of configurations and it is easy to use. Adapting your game to it may take a big amount of time, but it definitively pays off. Therefore, I see this as an investment.
How does it work?
Before that, you should definitely visit the project’s home page for the initial setup and the documentation. I already said that the framework is going to inject the dependencies but how?
Configuration
Every Dependency Injection framework expects the injection configuration from its client. In other words, you should provide binding information. Let’s see this example;
public class CoreInstaller : MonoInstaller { public override void InstallBindings() { Container.Bind<IScoreBoard>().To<ScoreBoard>().AsSingle().NonLazy(); Container.Bind<ScoreManager>().AsSingle().Lazy(); } }
In the configuration above we bind the concrete class to the interface. We define if it should have a single instance or not. Moreover, we can even configure if the instance should be created lazily. The features of the framework are way too impressive.
Injection
public class ScoreManager { private IScoreBoard _scoreBoard; [Inject] public ScoreManager(IScoreBoard iScoreBoard) { _scoreBoard = iScoreBoard; } }
You see that the framework will inject ScoreBoard instance when it sees IScoreBoard type. So, you don’t have to deal with object creation anymore. Zenject doesn’t require an interface in order to create an object. However, I would suggest you create well-defined interfaces. Once you have a good interface, managing the impact of the change will be fairly easy.
It is also possible to do field injection. For example;
public class ScoreManager { [Inject] private IScoreBoard _scoreBoard; public ScoreManager() { } }
This is less code for the same thing, right? However, there is a problem here. The dependencies of the ScoreManager is not explicit from outside. I always expose all the dependencies in the constructor. This is a good practice. Why?
- Constructor shows the dependencies of the class clearly.
- You would use the IDE more efficient without jumping into the classes.
- It is also a warning. If you have more than 3 dependencies there, you should stop. This is a clear sign that the class has a lot of duties. So, you may want to split it into two classes.
In case you would like to try it in your project, you can reach it from the link below.
https://github.com/modesttree/Zenject
Conclusion
I beg you to invest some time to learn it. I promise that you would be proud of your project structure. It will be clear and manageable. You will see how low coupled classes are increasing your productivity. Moreover, your code will be more readable and testable.
I plan to make a tutorial series for Zenject. There are good tutorials out there. However, I target a simple and clean explanation. It will be mainly for beginners.
If you have questions, please leave it as a comment.
please do the tutorial soon,
I prefer in actual feature implementation in some game
Hi Ric, I will do it. It is a bit busy now, but I have a plan to cover all the essential features of zenject for Unity.
Another clear and concise write up with great example code!