Game Management - Singleton VS Dependency Injection


This week I wanted to start by creating a management system. I didn't want to implement management logic individually for each manager. Rather, I wanted a base class to encapsulate that logic which the game manager, input manager, etc. could all derive from. First, let me start by explaining what I mean by 'manager', as the term might sound vague. A manager is a class which consolidates logic and data that doesn't belong to any other single class and should only really be done once. You wouldn't want to use multiple managers of a certain type at the same time. For example, no single object in the game should control things like the game state, which scene should be loaded, whether the UI is visible or not. All of these are global, one-time events. All exactly the sort of thing that a game manager could handle.

After hearing this definition of manager, some programmers might assume that I would implement it using a singleton. Now, I'm not going to say that the singleton pattern is evil. I think it can have its uses, especially on very small scale project. Also, if it works for others, that's great. Personally, I find that if it isn't actively causing huge project-wide issues, it has the potential to do so at any moment. It's like an accident waiting to happen, and not a pattern I would use on any project. There are many reasons why I think the singleton pattern is dangerous, but I'm far from alone in thinking this, so I won't get into that. To be fair, I gave it a shot and created several implementations. They were all functional, but as usual, they caused a headache rather than curing one. I ended up wasting most of the week exploring loads of different implementations and ideas. I learned a fair bit, but I wasn't happy with any of them. So, rather than use a singleton, I decided to learn something new - dependency injection.

Dependency injection (DI) might sound complicated, but it's actually fairly straightforward. It's a pattern that aims to decrease coupling by adding a layer of abstraction between the creation and access an object. That layer is called the 'container' / 'injector'. It handles creation of an object and allows other classes to access it. By creating the object, it 'injects' that type whenever the object is accessed. In the singleton pattern, the singleton class handles its own creation and destruction. The type of the instance can't really be modified. However, with DI, you can use any valid / inheriting type for that reference. Say I've implemented a basic game manager,  but I'm working on a new version that's more advanced and handles things like scene transitions. If I was using a singleton, I would have to replace every single reference of basic game manager instance with advanced game manager instance. That might not take long with a find and replace, but it's still error prone. Not to mention there's no guarantee that the advanced game manager will be a superset of the basic game manager. With Dependency Injection, it's as simple as changing a single type. DI is an incredible pattern that has allowed me to create a simple, streamlined management system. In fact, I didn't realize it before, but this is actually what Unreal uses. The GameMode acts as a container. I still have a lot to learn about this, but I plan on doing just that.

Leave a comment

Log in with itch.io to leave a comment.