Dependencies and IoC

by Taylor Hadden | 16:50

While I’m programming, there are two overarching goals that I’ve come to strive for while architecting and developing my applications:

  1. Single Solution: Objects and their functions exist to solve a single problem, and that problem is solved once.
  2. Minimum Dependency: Objects require and depend on as few other types and objects as possible in order to solve their problem.

I’ve found these two goals encourage me to build well-structured, easily maintained, and easily modified code.

Single Solution

The first concept is very simple. If you have a task that is required in multiple places and is algorithmically complex, you don’t want to have the same code duplicated all over your program. If you then find a bug in the algorithm or want to change how it functions, you now have to find all of the places where you copy-pasted that code and fix it.

This also applies to non-complex tasks. If you want to know whether or not your player can jump, you don’t want to manually check the requirements every time you need to know. You want to hide that functionality behind a property or function call so you can simply ask: if (player.CanJump()). Take advantage of every opportunity to save yourself time in the future.

Minimum Dependency

Minimizing the number of dependencies an object has is a broader concept. A dependency is any discrete link between one object and another. The most fundamental example of this is any direct instantiation or storage of a discrete type:

public class Foo {
	public string Name;
}

public class Boo {
	public Foo myFoo;	// Discrete storage

	public Boo() {
		myFoo = new Foo();	// Discrete instantiation
	}
}

But a dependency can also come from the dependencies of your dependencies:

public class Grue {
	public string Name;
}

public class Slew {
	private Grue myGrue

	// Discrete instantiation requirement
	public Slew Slew(Grue theGrue) {
		myGrue = theGrue;
	}
}

public class Moo {
	public Slew mySlew;

	public Moo() {
		// Because we depend on Slew, we also
		// depend on Slew's dependencies.
		mySlew = new Slew(new Grue());
	}
}

Minimizing dependency is a good thing because it makes objects more portable and easier to use. Fewer steps to access or create an object or feature means fewer ways to accidentally create a bug. Fewer arguments for instantiation or function calls also means that you don’t have to be dependent on the objects for the arguments as well. If you are constantly thinking about whether or not an object really needs all of the functionality and baggage that comes with a particular object, you will be more likely to reduce the requirements of an object to just what it needs to complete its objective.

Even the most basic of systems needs some manner of input, output, or assisting functionality, but we can work to reduce dependency to those objects by using interfaces:

public interface IFoo {
	public string Name { get; set; }
}

public class Foo : IFoo {
	public string Name { get; set; }
}

public class Boo {
	public IFoo myFoo;	// Storage of interface

	public Boo(IFoo myFoo) {
		this.myFoo = myFoo;
	}
}

Using an interface is a great way of providing just the methods and properties that your object needs while leaving the actual implementation of the functionality open-ended. It means that you can now completely change the objects that a class refers to without having to modify the class itself.

Additionally, instead of calling functions directly, you can use function delegates as callbacks (C# provides excellent support for this). The use of delegate callbacks (or event systems) means that an object isn’t making the decision over what functionality comes next, it is merely providing hooks that other objects can listen for and react to. For a more concrete example, instead of your Player class requiring a reference to your HUD in order to tell the health bar to flash when the Player takes damage, the Player class can provide a delegate callback for when it is damaged that the HUD class can hook into. This way, the Player doesn’t need to know anything about the HUD at all, removing that dependency.

public class Player {
	private float health = 100.0f;

	public float Health { get { return health; } }

	public Action onDamaged;

	public void DealDamage(float damage) {
		health -= damage;
		onDamaged();
	}
}

public class PlayerHUD : MonoBehavior {
	public Player player;

	void Start() {
		player.onDamaged = HandlePlayerDamage;
	}

	private void HandlePlayerDamage() {
		// Here, we can start the process of flashing the health bar
	}
}

Again, keeping a leash on your objects’ dependencies is a balancing act. Code is built to get stuff done, and sometimes classes need to be tightly coupled to one-another. It can be very beneficial to consider dependencies from the perspectives of “features.” A feature like a chat box might consist of several classes that work together tightly, but the rest of your program may only need to know about one or two. Dependencies are just as much about what one system knows as they are about what other objects or systems need to know in order to use that system.

Organizing Dependencies into Applications

If you just created objects with no dependencies, nothing would ever happen. An application needs to be able to do the following:

  1. Organize the creation of objects
  2. Organize the communication between objects
  3. Provide objects with common state information

To do this, I see application code as broken up into three layers:

  1. Framework code is things like the Unity Engine, or any other core library (such as C#’s Mono or .Net).
  2. Domain-Specific code solves the localized problems in your application. This includes everything from the code defining how the player moves to the code that defines how your scoring system works.
  3. Application-specific code organizes the domain-specific code so that everything communicates correctly and has the correct information. This can be thought of as the “glue” code.

In an application that has a large number of interconnected dependencies, your domain code and application code will likely be all mixed in. After all, the most straightforward way of calling a function is having a reference to that particular object and calling the function directly. However, often what’s really going on at the core of an application is a collection of systems that are mostly self-sufficient but have to communicate and react to various events. Thus, each component has its own goals, and only needs to listen to and broadcast a few events in order to function.

I shouldn’t have to provide a reference to my GUI in my player, nor should my game have to manually stop music and sounds whenever the user pauses. Instead, my player should alert the system that he has taken damage, and the GUI should react to that. My application should broadcast a “paused” event and my sound system should handle the pause and continuation of the appropriate music. Providing the right instances and connecting delegates to each other is the job of our glue code, but if we were to write all of that out by hand, our code would quickly become a twisted mess that would be more difficult to maintain than simply structuring our code in a more traditional manner. Luckily, we can avoid this problem.

Inversion of Control with Strange

StrangeIoC is an Inversion of Control framework. It provides very easy to use tools designed to solve the problem of organizing code and its dependencies. There are several useful systems provided with the framework, but the ones I want to bring up today help solve some of the issues that I talked about above: Dependency injection, Signals, and Commands.

Dependency Injection

Dependency injection allows a class to state that it expects to be handed an object of a certain type. This works best with interfaces, but it works for any type at all. Requesting an injection in a class is dead simple:

public class Moo {
	[Inject]
	public IFoo foo { get; set; }
	// An IFoo will be automatically added by Strange
// when the object is created
}

Injections are then linked to concrete types in the “Context” class, using the InjectionBinder. You can think of the Context as the switch-board for your application. You wire up your dependencies in one place, and then Strange takes over, providing instances of the correct type as necessary.

public class MyContext : MCVSContext {

	protected override void mapBindings() {
		// Provide a new instance of Foo every time
		// We fill an IFoo request
		injectionBinder.Bind<IFoo>().To<Foo>();

		// Provide a single instance of Boo every time
		// We fill an IBoo request
		injectionBinder.Bind<IBoo>().To<Boo>().ToSingleton();
	}
}

Strange’s MCVSContext is specifically tailored to Unity. The pattern set by the framework is that a small MonoBehavior rests on a root GameObject, and creates the Context upon Awakening. Any MonoBehavior that extends Strange’s View class and is placed on a child GameObject of the Context can use injection. I will talk more about the specifics of this in future posts.

Signals

Signals and Commands form the primary backbone of communication and application-level functionality in Strange. A Signal is essentially a collection of function delegates, and is a perfect way of easily expanding the single-delegate model I demonstrated earlier. An individual object can use it to easily announce various events and any number of other objects can listen for those events.

public class Player {
	...

	public Signal onDamaged = new Signal();

	public void DealDamage(float damage) {
		health -= damage;
		onDamaged.Dispatch();
	}
}

public class PlayerHUD : MonoBehavior {
	public Player player;

	void Start() {
		player.onDamaged.AddListener(HandlePlayerDamage);
	}

	void OnDestroy() {
		player.onDamaged.RemoveListener(HandlePlayerDamage);
	}

	private void HandlePlayerDamage() {
		// Here, we can start the process of flashing the health bar
	}
}

(In cases like this, I like to hide the actual usage of Signals behind my object’s interface and use the Signal as a backing object. That way, I can avoid the dependency of the exposure of the Signal object.)

Using Signals moves the responsibility for initiating action away from the initiator and onto the object that needs the action to be called. In the previous example of having the HUD flash when the player takes damage, now we can easily remove, change, and add functionality that occurs when the player takes damage, all without having to change the player code at all.

Strange’s signals have one key benefit over a more common event system as they allow for strongly typed arguments. We can expand the example above so that when the player gets hit, the “onHit” signal passes along the damage taken by that hit.

public class Player {
	...

	public Signal<float> onDamaged = new Signal<float>();

	public void DealDamage(float damage) {
		health -= damage;
		onDamaged.Dispatch(damage);
	}
}

public class PlayerHUD : MonoBehavior {
	...

	private void HandlePlayerDamage(float damage) {
		// Here, we can start the process of flashing the health bar
	}
}

Now, objects like our HUD system can differentiate between a large hit and a small hit very easily. Signals support up to five arguments (a restriction based on C#’s underlying Action class), and they will complain at compile time if another object tries to listen to a Signal without the correct arguments in its listener function. This makes Signals not only a fantastic device for alerting other objects about events, but also a great way to move data through your game.

Commands

Commands are highly specialized classes that perform specific functionality. Commands and Signals are used in partnership to organize functionality across an entire Context. The CommandBinder allows Signals to be tied directly to Commands, so that any time that Signal is Dispatched, a new Command is created, run, and disposed of. Like any other type, Signals can be bound in the Context and injected into objects, and any Signals bound with the CommandBinder are automatically setup in the InjectionBinder as singleton bindings. That means by injecting a signal into an object and calling Dispatch(), objects can trigger Commands.

// Creating a "stub" signal class allows for clear differentiation
public class StartSignal : Signal {
}

public class StartCommand : Command {
	// Inject the various systems you need to talk to to start the game.
	public override Execute() {
		// Do whatever you need to do to start the game.
	}
}

public class MyContext : MCVSContext {
	// Unbind EventCommandBinder and rebind to SingalCommandBinder
	// Necessary, as Strange uses an Event system by default.
	protected override void addCoreComponents() {
		base.addCoreComponents();
		injectionBinder.Unbind<ICommandBinder>();
		injectionBinder.Bind<ICommandBinder>().To<SignalCommandBinder>().ToSingleton();
	}
	...
	protected override void mapBindings() {
		...
		// The "Once()" statement means that once the command has been
		// run, the binding will be broken, and Dispatching StartSignal
		// will no longer run a StartCommand.
		commandBinder.Bind<StartSignal>().To<StartCommand>().Once();
		...
	}
}

Though the above example uses a signal and a command specifically for handling the start of the Context, I’ve found that Signals and Commands become most powerful when they aren’t necessarily created in pairs. Instead of injecting a Signal in order to run a specific Command, you should be injecting and dispatching a Signal when a certain event has occurred. That way, if you want to change how you react to that event, you only have to change the Command binding in the Context, and you don’t have to change which Signal is injected and dispatched.

Commands work best when they are handling a specific duty that requires interacting with a large number of systems. Because they use injection to get the objects they need (including any parameters passed along from the signal), the same Command can be used in many different contexts that use, for example, different implementations of the IPlayerManager interface without any issue. Because of this, Commands are an excellent answer to the Single Solution goal for specific but possibly wide-reaching tasks. I use Commands to handle when a remote player joins or drops from a multiplayer game and when I need to create a new entity in the game world (requiring initialization of data, controller, and view information), among many others.

A Practical Example: Injecting Game Settings

A common reason people use the dreaded hard-coded singleton is to provide access to various game settings. For example, you want to have your player be able to invert their mouse. With static variables, you would check GameSettings.isMouseInverted in the code that handles your mouse look, but that would mean that code requires the GameSettings class in order to work at all. Instead, we can use Strange’s injection system to provide access to specific setting objects, and now the mouse look code only needs to inject the specific settings that it needs to function.

public interface ISetting<V> {
	// These are separate to make the changing of a setting
	// a more deliberate act.
	V value { get; }
	void SetValue(V value);
}

public class Setting<V> : ISetting<V> {
	private V _value;

	public V value {
		get { return _value; }
	}

	public void SetValue(V value) {
		_value = value;
	}
}
public enum GameSettings {
	INVERT_Y_AXIS, LOOK_SPEED
}

public class MouseControl : View {
	[Inject(GameSettings.INVERT_Y_AXIS)]
	public ISetting<bool> invertY { get; set; }

	[Inject(GameSettings.LOOK_SPEED)]
	public ISetting<float> lookSpeed { get; set; }

	void Update() {
		...
		float mouseYDiff = Input.GetAxis("Mouse Y");
		if (invertY.value == true) {
			mouseYDiff *= -1;
		}
		...
		mouseYDiff *= lookSpeed.value;
		mouseXDiff *= lookSpeed.value;
		...
	}
}

The injections in this case use Strange’s named injection feature, using the GameSettings enum for names. Named injection allows you to differentiate between different bindings of the same type. In this particular example, there are no type duplicates (as generic classes of different types are treated as different types); in an actual game you may have many “ISetting<bool>”s. I will point out that using a common enum like GameSettings does create a similar sense of dependence as a static settings class; however, because the value will be stored in an instance, it is more flexible. If it still bothers you, consider having multiple groups of setting enums, such as a LookSettings, MovementSettings, and so on. Now, we fulfill these injections in the Context:

public class MyContext : MCVSContext {
	...
	protected override void mapBindings() {
		...
		Setting<bool> invertYAxis = new Setting<bool>();
		Setting<float> lookSpeed = new Setting<float>();
		lookSpeed.SetValue(1.0f);
		// Here, we could load the value of the setting from a file
		// or initialize it in another way

		injectionBinder.Bind<ISetting<bool>>().ToValue(invertYAxis).ToName(GameSettings.INVERT_Y_AXIS);
		injectionBinder.Bind<ISetting<float>>().ToValue(lookSpeed).ToName(GameSettings.LOOK_SPEED);
		...
	}
}

With these objects bound, the MouseControl script will be provided with these instances (and they will complain if a setting is not provided). Any other script that needs to use these settings can also inject them, and they will be provided with the same bound instances. Because any reference to a specific setting is looking at the same object, any change to those settings is inherently shared among all of the scripts that access them. In this way, each script only needs to know about the specific settings that it actually requires while still maintaining synchronized settings across the entire game.

Overview

The use of Strange allows you to easily build your application out of contained systems. Your inventory system, your HUD, and your enemies can be built as black boxes with input and output hooks with Injections and Signals, and all of those hooks can be connected in a single location. Additionally, broader, system-spanning functionality can be defined in very specialized Commands, and the sequence and triggers of those Commands are set up in the same place.

While it introduces a sizeable learning curve, I’ve grown to very much enjoy using Strange. Once you wrap your head around its concepts and how best to build things for it, it actually becomes very liberating. I know that if I need access to something in an object, I can simply inject it. It also encourages  you to write code with very clear responsibilities and boundaries.

This discussion of some of the essential components of Strange has only scratched the surface. If you want to immerse yourself fully, there is an exhaustive description here.

  • Raziel Anarki

    This is hands down the best Strange introduction I’ve read, too bad it’s hard to find on google…. anyways: Thanks!

    • Taylor

      Awesome, thank you! I’m glad somebody has gotten some utility out of it.

  • miteshsoni1995

    This was really awesome, I actually understand something.
    can any one please tell me what does [Inject] keyword does?