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 ‘new’ keyword. We are able to do this successfully, thanks to a Dependency Injection framework. So, I will give the basic explanation of the pattern and its advantages. In addition to that, I will introduce you a nicely designed dependency injection framework – Zenject – written for 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 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 for the  ScoreManager. Besides, imagine that ScoreBoard is used in other classes as well. After that, more modification would require for 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 an higher class. 

READ  Hello World! (Not WordPress' auto post)

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.

Traditional Way of Dependency Management
Traditional Way of Dependency Management

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. 

Dependency Injected way of dependency management 

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 free and open source. Most of the platforms are supported. It provides 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 it works?

Before that, you should definitely visit the projects 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 the 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 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 the object creation anymore. Zenject doesn’t require an interface in order to create an object. However, I would suggest you to create well defined interfaces. Once you have a good interface, managing the impact of the change will be fairly easy. 

READ  Why Modular Game Development and How to Do It with Unity?

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.

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 aim the simple and clean explanation. It will be mainly for the beginners. 

If you have questions,  please leave it as comment.