Dependency injection

From Unify Community Wiki
Jump to: navigation, search


Author: Lonewolfwilliams (http://www.lonewolfwilliams.com)

Contents

Dependency Injection

Dependency Injection is a complicated name for a simple concept, you are probably already using dependency injection without even knowing it! The basic principle is that we don't retain references to instances of objects as members of classes (Dependencies), wherever possible. To achieve this we pass (Inject) the object reference into the methods that act on them, the so called 'inversion of control principle'. As I already mentioned you may already have done something similar to this, maybe you have a class constructor that takes another class instance as a parameter:

 
Public Class MyClass
{
   uint[] myPixelData;
 
   Public MyClass(MyImageDataClass image)
   {
      myPixelData = image.pixelData;
   }
}

In this example we retain the data from the imageClass, but not the object reference, MyImageDataClass is a Dependency Injection.

Why Inject Dependencies ?

The advantages of dependency injection mainly come from the increased decoupling between class instances in your code (note that this doesn't eliminate dependencies, the class still needs to 'know' about the injected class at compile-time, to take it as a parameter, but more on that later.) By promoting decoupling you get:

  • Fewer references held, making 'memory leaks' (where objects cannot be deallocated by garbage collection, due to reference counts greater than zero) less likely, and easier to spot.
  • Narrower scope for where objects interact, making the impact of changes to their interfaces easier to identify
  • Promoting the significance of the objects interface, making it more likely you will write classes functionality specific to it's usage, the so called 'single responsibility principle'

A Dependency Injection Container / the model locator pattern

Now that we have a basic understanding of the principle of dependency injection, and some of its advantages, I would like to introduce a generic implementation of this pattern, often referred to as a Dependency Injection Container.

In my opinion, one significant advantage of a generic implementation such as this, is that it automatically manages singleton instances of the classes you choose to inject, those of you who don't like the Singleton pattern can promote single instances, without restricting the users of your class to single instances, and so retaining choice.

 
static class ModelLocator
{
   static Dictionary<Type, object> m_instances;
   public static object GetModelInstance<T>()
   {
      //lazy initialiser
      if (m_instances == null)
      {
         m_instances = new Dictionary<Type, object>();
      }
 
      Type forType = typeof(T);
      object modelInstance = null;
      m_instances.TryGetValue(forType, out modelInstance);
 
      if (modelInstance == null)
      {
         modelInstance = Activator.CreateInstance<T>() as object;
         m_instances.Add(forType, modelInstance);
      }
 
      return modelInstance;
   }
 
   //note that static classes are never Garbage Collected (for fairly obvious reasons) so we
   //need an alternative to IDisposable for releasing the references...
   public static void Cleanup()
   {
      UnityEngine.Debug.Log("ModelLocator::cleanup");
      foreach (KeyValuePair<Type, object> kvp in m_instances)
      {
         if (kvp.Value is IDisposable)
         {
            (kvp.Value as IDisposable).Dispose();
         }
      }
 
      m_instances.Clear();
   }
}

Usage

Whenever your class methods require a reference to a class instance:

 
var MyClass = ModelLocator.GetInstance<MyClass>() as MyClass;
 
//you now have a reference:
MyClass.method();

In terms of Unity I have found this is particularly useful technique for separating Model code or 'systems' from View-Model code or Monobehaviours. Think about it this way:

I want to keep scores in my game that are independent of the current Level being played, I implement my levels as separate scenes, and create a scoreKeeping Class, with a simple playerScore Property:

public class ScoreKeeper
{
   int m_score = 0;
   pubic Score
   {
      get
      {
         return m_score;
      }
      set
      {
         m_score = value;
      }
   }
}

Obviously we want the score to persist between scenes, and this is where our container comes in, adding a Monobehaviour component to our player GameObject, we can do the following:

 
public class PlayerScoreBinding:MonoBehaviour
{
   void Awake()
   {
      var score = ModelLocator.GetModelInstance<ScoreKeeper>() as ScoreKeeper;
      //display the player score...
   }
 
   void Update()
   {
      var score = ModelLocator.GetModelInstance<ScoreKeeper>() as ScoreKeeper;
      //modify the player score...
   }
}

Inversion of Control

The normal practice in Unity to do the above would be through the use of 'Manager' objects, empty game objects which have scripts attached that persist via calls to DontDestroyOnLoad(). If you imagine the control flow for this implementation the manager 'pushes' data down to the GameObjects in the scene: On Awake find the component 'bar' on the game object called 'foo' and set the score property to N... Using dependency injection the control flow is reversed and data is 'pulled down' to each game object: (in each GameObject in the scene) On Awake get a reference to the model 'foo' and then get the score proeprty. For this reason, inversion of control is sometimes called 'the hollywood principle' (don't call us, we'll call you) ^_^


Advantages of Inversion of Control via Dependency Injection
Hopefully from the example above you can see that:

  • There is a tighter coupling between models (abstracted game logic systems) and view models (Monobehaviours) this means less reliance on lookups, or 'inlet' references on script components being defined (and the resultant human error introduced as a result.)
  • Lazy initialisation on the container makes sure that the (mysterious) order of the 'Awake' callbacks on GameObjects in Unity has no effect on the availability of the model code at run-time, reducing issues arising from compilation orders
  • There is less reliance on empty game objects, which can clutter the scene, and make it harder to see 'what does what' in your codebase (because it is distributed across the editor)
  • This practice can allow the use of namespaces, since you are not writing your model or 'system' code in monobehaviour classes
  • For me personally, it helps promote an MVVM style architecture for Unity projects, which makes me feel more comfortable.


Disadvantages of Inversion of Control via Depenedency Injection
There are some Disadvantages too, one of which I can identify here, others I will probably learn as time goes on ^_^

  • As far as I know this technique is not suitable for communication between UnityScript and C# implementations
  • Inversion of control can't work with Monobehaviour references, since these are instantiated at runtime by UnityEngine

Improvements

I am far from being an expert, so naturally this class could of course be improved upon, two things I can immediately think of are:

  1. Injecting against interfaces, which would make it easy to replace, say a webservice API with a dummy for offline testing
  2. adding a method that returns a new instance every time, promoting choice instead of enforcing a singleton pattern
  3. adding a way to pass parameters to the constructor

Issues

I recently discovered that there is an issue using this pattern: Imagine that you ask for a new instance of foo from the modellocator, now imagine that (for whatever reason) in the constructor of foo you ask for a new instance of foo from the modellocator; because we are within the scope of the constructor, then an instance of foo hasn't been added to the dictionary of modellocator yet, so a new instance of foo gets created - this will create an infinite loop :(

The above issue isn't such a big deal, but things can get trickier if for example you create a new instance of bar in foo, and bar asks for an instance of foo from the modellocator, kind of like a circular dependency. Another possibility is that bar dispatches an event inside the constructor that triggers a method call in bar that asks for an instance of foo... A more complex variation on this theme took me a while to debug when it happened to me!

Personal tools
Namespaces

Variants
Actions
Navigation
Extras
Toolbox