Dependency Injection with Unity - Part 1

Table of Contents

The problem: you have dependencies

When you program a game (or any other piece of software for that matter) almost everything has dependencies to something else. For example you may have a service that spawns units in your game.

public class UnitService {
  public void SpawnUnit(int type, Vector3 position) {
  }
}

This service needs to know what unit types are there and what values they have. Also these values are configurable in some way so you can easily tweak your game’s balancing without having to touch a million files. So you may have some ConfigurationService:

public class ConfigurationService {
  public UnitInfo GetUnitInfoForType(int type) {
      // reads a config file and
      // gives back some information object with unit 
      // values, the prefab, etc.
  }
}

UnitService needs to know the unit values before it can spawn a unit. So it should probably access the configuration service somehow to get this information. It should not read the configuration itself, because it’s a service about managing units, not reading configuration. How would UnitService access ConfigurationService?

Approach 1: Using singletons

A simple solution is to make the ConfigurationService a singleton. TL;DR - a singleton is an object that can only exist once and that you can access from everywhere.

public class ConfigurationService {
  public static ConfigurationService Instance {get;} = 
          new ConfigurationService();
  
  private ConfigurationService() {}
  
  public UnitInfo GetUnitInfoForType(int type) {
      ...
  }
}

Ok great, now your UnitService can easily get access to the ConfigurationService like this:

public class UnitService {
  public void SpawnUnit(int type, Vector3 position) {
      // get information about the unit to spawn
      var unitInfo = ConfigurationService
              .Instance
              .GetUnitInfoForType(type);
      
      // do the rest of spawning here. 
  }
}

Done. That was quite easy. Except it doesn’t work so well. First off, you will quickly get in trouble when you want to unit-test UnitService. You have no control over how UnitService gets the ConfigurationService because it is hard-coded into the UnitService. So if you want to test UnitService in isolation you really can’t because it will always fire up a ConfigurationService as well and in addition this ConfigurationService wants to read a configuration file so suddenly your simple unit test just exploded into a full blown integration test.

Another drawback of this approach is that you hide dependencies of your objects somewhere in a method implementation which makes it very hard to reason about which object is using which other objects to do stuff. In any non-trivial project this is a dead-sure recipe for creating an unmanageable mess where you have hundreds of intertwined components and no idea which component is using what. Also components may have to do some initialization so a simple singleton may not even cut it. What if the ConfigurationService needs to connect to some web service or database to get the configuration? What happens if this connection fails? Who handles the error?

Approach 2: The service locator

With the service locator pattern you can get around some of the drawbacks of the singleton pattern. A service locator is a class that produces an instance of your dependency if you ask for it:

public class ServiceLocator {
  public T Get<T>() {
      // ... code here 
  } 
}

public class UnitService {
  private ServiceLocator _serviceLocator;
  
  public UnitService(ServiceLocator serviceLocator) {
    _serviceLocator = serviceLocator;
  }
  
  public void SpawnUnit(int type, Vector3 position) {
      // get information about the unit to spawn
      var unitInfo = _serviceLocator
              .Get<ConfigurationService>()
              .GetUnitInfoForType(type);
      
      // do the rest of spawning here. 
  }
}

The big advantage of this is that now UnitService is no longer concerned about how a ConfigurationService is constructed and initialized. The service locator is responsible for creating and initializing the dependencies while your game services can just consume the dependencies from the service locator. Also if you make your ServiceLocator an interface instead of a class, you can swap out implementations in your unit tests and can test using a fake ConfigurationService.

So the service locator fixes quite a few of the problems of the singleton. Except, that you still don’t know which dependencies each component of your game has, because it’s still buried somewhere in the code. And you still can construct objects that have unsatisfied dependencies because if your service locator fails you will only realize it later in the game when the functionality that requires a dependency is actually used.

If a component can fail during initialization (e.g. a web service being unreachable) then you need to spread error handling for this all over your project wherever this component is used because every invocation of the service locator may be the first that initializes the component and triggers the error.

When you unit-test a component using a service locator you need to look into the code and find out what it needs in order to prepare your fake service locator for it. While that may work for you, I found it to be tedious and error-prone in my projects as you will only see errors at runtime.

Approach 3: Using dependency injection

With dependency injection, just like with the service locator, your service will no longer be concerned with how dependencies are created and initialized. In addition to that, your service will also not be concerned with how dependencies are acquired. Instead of using a ServiceLocator and let UnitService acquire ConfigurationService, you create ConfigurationService first and give it as a constructor parameter to UnitService:

public class UnitService {
  private readonly ConfigurationService _configurationService;
  
  public UnitService(ConfigurationService configurationService) {
      this._configurationService = configurationService;
  }
  
  public void SpawnUnit(int type, Vector3 position) {
      // get information about the unit to spawn
      var unitInfo = _configurationService
              .GetUnitInfoForType(type);
      
      // do the rest of spawning here. 
  }
}

// some bootstrapping code
var configurationService = new ConfigurationService();
var unitService = new UnitService(configurationService);

Now UnitService does not need to know how to construct a ConfigurationService and in addition it also doesn’t need to know how get hold of one. It just gets everything injected as constructor parameters.

You may already have seen something similar, when you use MonoBehaviours in your game. These have public variables where you can plug in connections to other game objects in your scene. This is also some form of dependency injection. For example your units may have a commander which they follow:

public class Unit : MonoBehaviour {
    public Commander commander;
    
    public void Update() {
        var position = commander.transform.position;
        // follow commander.
    }
}

Now in your scene you can drag the commander directly into the Commander field of your Unit. Again the Unit is not concerned about how a Commander is created or acquired, it just provides some means to inject the Commander dependency. So if you are using MonoBehaviours you may have used dependency injection all the time without even knowing it!

By initializing your components this way you also can effectively disconnect the construction phase from the usage phase. Because your components always get ready-made components injected they don’t need to handle errors that may occur during the initialization phase of a component. These can be handled by some bootstrapping code in a central place.

Properties vs. constructor injection

Maybe you’re asking yourself now what is better: using properties (or public variables) for dependency injection like the MonoBehaviours do - or using constructors?

In my experience you should go for constructors whenever you can. With constructors you can see directly from the constructor parameters which dependencies a component has. If your dependencies are constructor parameters you cannot construct an invalid object with missing dependencies. This also greatly helps with testability, as you don’t need to scan a lot of properties if they hold dependencies that you need to fill. Instead you can use the auto-complete function of your IDE to show you the required dependencies when you call the constructor. Finally, with constructor injection it’s impossible to build circular dependency chains (like A needs B, B needs C and C needs A).

Sometimes you don’t have control over the constructors, though. For example, MonoBehaviours have their lifecycle controlled by the Unity runtime and you cannot just give them a constructor with dependencies because Unity just doesn’t work that way. Such objects have to be late-initialized after they are created and the only way to do this is using some properties (or initializer methods). It has the drawback that you cannot easily see the dependencies and you can forget to initialize some property. But this is what enables easy editor integration for Unity, so it’s a trade-off you have to make if you want to configure your game objects in the editor.

Summary

In your game code you always have dependencies between components. There are various approaches for making dependencies available, three of which are:

  • The singleton pattern, where each component is a singleton and is referenced by other components as such. This pattern is difficult to maintain and test for larger projects, so it’s only really an option for small prototypes, where unit-testing is not a consideration.
  • The service locator pattern, where each component refers to a service locator component which is responsible for creating components. It solves most of the problems if the singleton pattern but still hides dependencies of your components from you, making it harder to maintain in larger projects. It also makes error handling during initialization difficult as you cannot control when a component is created. Still it may be a suitable choice for small to medium-sized projects.
  • The dependency injection pattern, where each component gets its dependencies as constructor arguments or properties. This makes testing straightforward and clearly shows dependencies of your components (if constructor arguments are used). It allows for centralized error handling in the construction phase. This pattern scales well over larger projects.

After reading this article, you should have some good grasp on why dependency injection is a good thing to have. In part 2 of this article, I will cover concrete approaches on how you can use the dependency injection pattern to bootstrap your game in the Unity game engine.