Check the original blog post at The Gamedev Guru: The "KISS" Dependency Injection in Unity
Two questions for you:
- Are you using a dependency injection system in Unity?
- If so, can you understand, read, debug and maintain the code of your dependency injection system in less than 10 minutes?
If the answer to any is NO, this post might help :-)
Table of Content
- Why Use Dependency Injection in Unity?
- What is Dependency Injection (DI)?
- My Main Problem With (Most) Existing Unity Dependency Injection Systems
- My KISS Approach to Dependency Injection in Unity
- What's Next?
datadriveninvestor.com
Why Use Dependency Injection in Unity?
Here’s one simple but (sadly) often ignored principle in software development: SRP.
That reads: Single Responsibility Principle.
And that means: every class, every function should have one and only one responsibility.
Some examples:
- Class PlayerProfile. This class should only contain data related to the player (level, name) and functions directly related to reading/writing them.
The PlayerProfile class should NOT show level-up popups nor grant xp-related achievements. - Function int CalculateGainedExperience(int level). This function should just calculate how much experience you gain when you beat a level.
It should NOT grant xp to the player, nor write any data in the player profile. In fact, it should have no side effects. Ninguno.
This is a common mistake that violates SRP:
class Player
{
void LevelUp()
{
Backend.Instance.SendLevelUp();
}
}
You see that Backend.Instance access?
This access violates the Single-Responsibility Principle.
The LevelUp function should just do a level up. It is not part of its job description to find a reference to a backend system (in this case, the singleton).
So this function should just invoke the SendLevelUp function through a backend reference that someone passed it.
This is why breaking SRP in this manner is a problem:
- The next time you change your specific backend implementation, gl hf changing all references.
- Creating unit tests that mock the backend is hard, as you can’t easily change the references
- Getting references makes your code messier and harder to read for everyone
More typical examples of SRP violation:
- FindObjectOfType<Backend>()
- GetComponent<Backend>()
I can sum it up this way: finding a reference to an object is not your class/function’s problem.
So, whose problem is it then?
Finding object references is the Dependency Injector’s problem.
What is Dependency Injection (DI)?
To keep it short, a DI system does two things:
- Keep track of your main project objects, e.g. Backend, VO, Localization, Logger
- Assign/Inject references into your objects as they require
Think of a DI system as the table of contents of a book. A ToC with a list of chapters ( Backend) and their page/location (0x123123).
This is how you 1. Keep track of your references:
Injector.Bind<IBackend>(new Backend());
Injector.Bind<ILogger>(new LoggerFile());
// ...
And this is how you 2. Inject your references into your objects:
class Player
{
[Inject] private IBackend _backend;
void LevelUp()
{
_backend.SendLevelUp();
}
}
// ...
// ...
Injector.Inject(myPlayerObject);
That’s it.
When it comes to choosing a DI system, there are many third-party libraries out there like Zenject and StrangeIoC.
However, I have a big problem with most...
Woah! A 'Lightweight' framework, sure
My Main Problem With (Most) Existing Unity Dependency Injection Systems
The main problem I have is that they are not simple, but rather quite complex.
In other words, most DI implementations don’t follow the KISS approach (KISS = Keep it Simple Stupid).
It’s not uncommon to see DI systems with 20k lines of code or more (I’m looking at you, Zenject).
And this is a huge problem because of the effort and time it takes to:
- Understand its system
- Read its code
- Maintain it
- Debug it
I used Zenject a few times.
Can you guess what happened after running into problems?
I spent DAYS understanding and debugging its code base. Several times.
How much time did I save by using Zenject?
None. In fact, it was negative value. All in all, I spent more time working on a DI system than the DI system working for me.
Not great...
So, what to do about it?
My KISS Approach to Dependency Injection in Unity
Here’s a piece of advice that you can feel free to ignore (and eventually regret):
Every time you evaluate using a third-party library in your PRODUCTION-level project, investigate it well.
Ask yourself these questions about any library you want to use:
- Can I understand what it does without reading its documentation within 10 minutes?
- Can I effectively read most of its code within these 10 minutes?
- Could I easily debug it whenever I run into problems within 10 minutes?
It might be 10 minutes, it might be 1 hour. It depends on the system we’re talking about.
At its essence, the key part is this: you want to Keep It Simple Stupid.
Ever since I spent days working on a DI system rather than the DI system working for me, I adhere pretty much to the KISS rule whenever I can.
Sure, that’s not always the case with libraries like Photon or engines like Unity, but those are big for a reason.
A Dependency Injection system can be and should be simple.
Again, simple means:
- You can easily understand it
- You can quickly debug it when problems arise
- You can simply extend it if you really have to
Following the KISS principle is a professional matter.
So no more bells and whistles, thanks.
What’s Next?
You can watch the full lesson on my approach to dependency injection with your 7-day trial of the Unity Performance Taskforce.
This (live) lesson includes everything you need to quickly get started:
- A proper KISS DI system (1 file, < 150 lines)
- Examples
- Live QA discussion
Just enroll in the Unity Performance Taskforce and simply head to Week 025.
(& grab some popcorn :-))
Ruben