D20 RPG – Refactor Injection

In the past, I have often created multiple iterations of prototypes before ever posting any content about it. This project has been raw – you are seeing it as I develop it. The hopeful benefit to that approach is that I am better able to share my thought processes behind each step of the way. That includes showing when I may decide I’ve gone down a wrong road, or at least that there is a better way to do something. While refactoring isn’t necessarily the most fun thing to work on, it is an important part of the process, and I think you’ll enjoy some of the changes I will make.

Overview

Just in case you haven’t already read them, I would recommend you take a quick detour and read these two posts, since they are very relevant to this lesson:

In those posts I started playing around with attributes and reflection to do some pretty interesting things. One benefit is a new expressive way to give one system “Priority” over another – whether in the order it is run from a list, or even to the order that systems are instantiated. Another benefit was an alternative to the way I have used injectors – instead I can allow my systems to be automatically instantiated and injected, again supporting an expressive priority if desired.

Getting Started

Feel free to continue from where we left off, or download and use this project here. It has everything we did in the previous lesson ready to go.

Next import this package into your project. It includes scripts that were initially created during the posts mentioned above, although I have added and changed a few things which I will discuss during this post. The breakdown of included scripts looks like this:

Based on the “Attributes & Execution Order” lesson:

  • Priority – Not modified
  • PriorityAttribute – Not modified (file also includes)
    • PriorityFixedAttribute
    • PriorityBeforeAttribute
    • PriorityAfterAttribute
    • PriorityTypeExtensions
  • PriorityList – Not modified

Based on the “Reflecting on Dependency” lesson:

  • Dependency – Modified, and ultimately ignored in favor of IDependency
  • DependencyAttribute – Modified
  • DependencyCollection – New
  • DependencyCollectionAttribute – New
  • DependencyFactoryAttribute – New
  • DependencyInjection – Modified
  • InjectableAttribute – Not modified

Dependency Notes

I had originally planned on integrating this class into the project, and some of the unit tests had existing functionality to clean up (remove) the dependencies that they had added. Therefore I added a “Reset” method (this is shown for convenience, but most of the snippets in this lesson are already included in the scripts that were just imported – I will tell you when you should make changes):

public static void Reset()
{
    _instance = default;
}

In the blog post where I originally created this class, I gave one reason being that I didn’t love the way that the interface inheritance looked. While it does add a little “boilerplate”, the extra code was not without reason. I had forgotten one of the motivations behind the “Interface Injection” pattern in the first place, which was to remove the coupling of the “IOC Container” itself. I see two options to pick from:

  1. Reference the dependency in the declaration code
  2. Reference the dependency in the consumer code

In code, those options look like this:

// Definition code like this...
public interface IDataSystem : IDependency<IDataSystem>

// Allows consumer code like this...
IDataSystem.Resolve().Create();

// Whereas definition code like this...
public interface IDataSystem

// Would require consumer code like this...
Dependency<IDataSystem>.Resolve().Create();

I think that in general I can assume that a system will be “consumed” multiple times, while it is only defined once. Therefore I can be lazier by coupling the dependency to the declaration. Also, for the sake of “this” refactor, it requires less changes – win win!

Dependency Attribute Notes

As a result of the decision to keep the preexisting IDependency interface instead of the new Dependency class, I needed to make a couple small changes to the DependencyAttribute’s Inject method. First, I needed to swap out the generic type:

// I replaced this:
var generic = typeof(Dependency<>);

// With this:
var generic = typeof(IDependency<>);

Then, because IDependency was using method overloading, it was necessary to be more specific in how I grab which “Register” I meant to use:

// I replaced this:
MethodInfo method = specific.GetMethod("Register", BindingFlags.Public | BindingFlags.Static);

// With this:
MethodInfo method = specific.GetMethod("Register", new Type[] { _type });

Dependency Injection Notes

This class enumerates over EVERY Type in the assembly and instantiates classes (or at least tries to) of any class where it, or a super class, has an injectable attribute. In my simple demo project that was no problem. In a larger project like this one, I realize that some of those classes are abstract, and you are not allowed to create instances of abstract classes. Therefore I put in a quick check for that, to skip these cases:

if (type.IsAbstract)
    continue;

Dependency Collection Notes

While I didn’t end up using the new Dependency class, I did create and use something very similar, which I called the DependencyCollection. It looks like this:

public class DependencyCollection<T>
{
    public static PriorityList<T> Collection => _collection;

    private static PriorityList<T> _collection = new PriorityList<T>();

    public static void Register(T instance)
    {
        _collection.Add(instance);
    }
}

It has an associated attribute that looks like this:

using System;
using System.Reflection;

public class DependencyCollectionAttribute : InjectableAttribute
{
    private Type _type;

    public DependencyCollectionAttribute(Type type)
    {
        _type = type;
    }

    public override void Inject(object instance)
    {
        var generic = typeof(DependencyCollection<>);
        var specific = generic.MakeGenericType(_type);
        MethodInfo method = specific.GetMethod("Register", BindingFlags.Public | BindingFlags.Static);
        method.Invoke(null, new[] { instance });
    }
}

Whereas the Dependency class was created to let me inject a single dependency, much like one would use a singleton for a system, the new DependencyCollection class can hold a bunch of dependencies that all conform to the same interface. Why would I need such a thing? I’m glad you asked, here is one example: in the current version of this project, I have special systems that handle “SetUp” and “TearDown”. At the moment, they allow convenient callbacks to do things like handling event registration.

In the current implementation, it was necessary for these two systems to be the first systems I injected, so that other systems could register with them in their constructors. This worked because they were listed first in the Injector, which in my opinion was slightly fragile, but ok since I left handy comments.

Now that systems can be “auto-injected” thanks to the new features we just imported, it would be possible to replace the fragile bit of that code by simply adding a high “Priority” attribute to those two systems and they would be guaranteed to be created before the other lower priority systems. I could have left everything else largely alone.

However, the whole “purpose” of having those systems created first was to simply provide a place to store a collection of the actions that needed to be run. Since the DependencyCollection has static storage, it will always be available no matter what. Even better, is that rather than storing actions, I can just store a reference to anything matching the desired interface.

Thanks to all of that, we can now replace the code for those two systems. Open SetUpSystem.cs and replace its contents with the following:

public interface ISetup
{
    void SetUp();
}

public interface ISetUpSystem : IDependency<ISetUpSystem>
{
    void SetUp();
}

[Dependency(typeof(ISetUpSystem))]
public class SetUpSystem : ISetUpSystem
{
    public void SetUp()
    {
        var systems = DependencyCollection<ISetup>.Collection;
        foreach (var system in systems)
            system.SetUp();
    }
}

Next, open TearDownSystem.cs and replace its contents with the following:

public interface ITearDown
{
    void TearDown();
}

public interface ITearDownSystem : IDependency<ITearDownSystem>
{
    void TearDown();
}

[Dependency(typeof(ITearDownSystem))]
public class TearDownSystem : ITearDownSystem
{
    public void TearDown()
    {
        var systems = DependencyCollection<ITearDown>.Collection;
        foreach (var system in systems)
            system.TearDown();
    }
}

Of course this requires a few cascading changes. For example, in the EntityTableSystem. We can start by changing its class definition with some new interface conformances and attributes:

[DependencyCollection(typeof(ISetup))]
[DependencyCollection(typeof(ITearDown))]
public abstract class EntityTableSystem<T> : IEntityTableSystem<T>, ISetup, ITearDown

Then you can delete the class constructor, since all of its functionality has been replaced by the new interface implementations and corresponding dependency collection attributes.

// Delete this
public EntityTableSystem()
{
    ISetUpSystem setup;
    if (ISetUpSystem.TryResolve(out setup))
        setup.Add(SetUp);

    ITearDownSystem tearDown;
    if (ITearDownSystem.TryResolve(out tearDown))
        tearDown.Add(TearDown);
}

The EntitySetSystem will need a similar treatment, and is left as an exercise to the reader. Don’t worry, if that is too difficult, you can always take a look at the completed project example at the end of this lesson.

Dependency Factory Notes

Another “challenge” point of trying to use the “auto-injection” idea is that it relies on all of the injected classes using only a default parameterless constructor. In this project some systems, like the DataSystem, require a parameter.

Other systems, like the DamageSystem, do use a parameterless constructor, but were specially configured inside the injector. In this case, there are several DamageTypeSystem instances that are created and added to it, and each of THOSE required parameters in the constructor.

The solution I came up with to handle these challenges (while maintaining the features of priority based auto-creation and auto-injection) was to create a new DependencyFactory. These factory classes are marked by a new attribute that causes the factory to be created and then used to create and inject a new system. The “factory” is really just an interface that concrete versions will need to inherit (the following snippets have already been imported and are just shown for convenience):

public interface IDependencyFactory<T>
{
    T Create();
}

It will work with another new attribute:

using System;
using System.Reflection;

public class DependencyFactoryAttribute : InjectableAttribute
{
    private Type _type;

    public DependencyFactoryAttribute(Type type)
    {
        _type = type;
    }

    public override void Inject(object instance)
    {
        Register(Create(instance));
    }

    private object Create(object factory)
    {
        var generic = typeof(IDependencyFactory<>);
        var specific = generic.MakeGenericType(_type);
        MethodInfo method = specific.GetMethod("Create");
        return method.Invoke(factory, null);
    }

    private void Register(object instance)
    {
        var generic = typeof(IDependency<>);
        var specific = generic.MakeGenericType(_type);
        MethodInfo method = specific.GetMethod("Register", new Type[] { _type });
        method.Invoke(null, new[] { instance });
    }
}

Let’s put the above new scripts into practice. Create a new script named DataStoreFactory (in the same location as the DataSystem) and add the following:

[DependencyFactory(typeof(IDataStore))]
public class DataStoreFactory : IDependencyFactory<IDataStore>
{
    public IDataStore Create()
    {
        return new DataStore("GameData");
    }
}

Next, create a new script named DamageSystemFactory (in the same location as the DamageSystem) and add the following:

[DependencyFactory(typeof(IDamageSystem))]
public class DamageSystemFactory : IDependencyFactory<IDamageSystem>
{
    public IDamageSystem Create()
    {
        IDamageSystem system = new DamageSystem();
        system.Add(new DamageTypeSystem("physical", new string[] { "bludgeoning", "piercing", "slashing" }));
        system.Add(new DamageTypeSystem("energy", new string[] { "acid", "cold", "electricity", "fire", "sonic" }));
        system.Add(new DamageTypeSystem("alignment", new string[] { "chaotic", "evil", "good", "lawful" }));
        system.Add(new DamageTypeSystem("mental", new string[0]));
        system.Add(new DamageTypeSystem("poison", new string[0]));
        system.Add(new DamageTypeSystem("bleed", new string[0]));
        system.Add(new DamageTypeSystem("precision", new string[0]));
        system.Add(new MaterialDamageTypeSystem());
        return system;
    }
}

Onward to the Refactor

The real goal of this lesson was to get rid of all of the manual “Injector” classes. This project has A LOT of injected classes, so this will be a bit of a chore for anyone who wants to actually follow along. Sorry about that. This is partly because I have been creating the project without a solid grasp of just how much control I will need or where I will need it. It is entirely possible that many of the classes can be refactored into a single class, but I won’t know until I have finished implementing about a bazillion features.

I’ll describe the basic process I used. First, I opened the Injector class. I’ve already shown the changes I made to the SetUpSystem and TearDownSystem, so you can go ahead and delete those two registration lines. Continuing to work from top to bottom, I see that I daisy chain injection using another injector, ActionInjector, so I open that next. It has just a single registration, for ICheckSystem. Go ahead and open that file. All we need to do is add an attribute to the class definition like so:

[Dependency(typeof(ICheckSystem))]
public class CheckSystem : ICheckSystem

And now the system will be automatically created and injected. That means I can remove the manual registration from the ActionInjector. And since the ActionInjector is now empty, I go ahead and delete it, and remove its reference from the Injector class.

Continue that same process for every other manual registration (excluding any examples for which I created Dependency Factories – just delete those lines when you get to them) and eventually you won’t need the Injector either.

The final step will be in the AppFlow

// Replace this...
Injector.Inject();

// With this...
new DependencyInjection().Init();

The AppFlow class is like our bootstrap part of the app. It determines when dependency injection occurs, but the new DependencyInjection class handles “how” it occurs, and the order of creation for each dependency, etc.

Some of the unit tests also used the “injector” classes so the test could run with only what was needed. If you don’t care about maintaining the tests, you can always delete them. If you do want to maintain them, you can either manually inject the classes you want or use the DependencyInjection class instead.

Good luck! Don’t forget that you can also just use the completed project example at the end of this post.

Summary

In this lesson I decided to bring some of my experimental blog post content into this project. It required some refactoring, but I believe I now have more convenient, expressive and stable code in place. Systems are now automatically created and injected, and it is possible to have complete control over the order that it all happens (if needed).

If you got stuck along the way, feel free to download the finished project for this lesson here.

If you find value in my blog, you can support its continued development by becoming my patron. Visit my Patreon page here. Thanks!

Leave a Reply

Your email address will not be published. Required fields are marked *