Every time I start a new project, I spend a fair amount of time reflecting on pain points from previous projects, what I enjoyed working with, things I would still like to try, current standards and patterns, and how my fellow engineers might react to something I write. One of the big problems every project faces: how to access what you need, from where you need it. I’ve done a bit of experimenting and decided to share something I think is worth further exploration.
Gasp, the Singleton
public class SomeSystem { // Global access to a single shared instance public static SomeSystem Shared { get; private set; } = new SomeSystem(); // Private constructor, so only one can be instantiated private SomeSystem() {} // Functionality available as needed public void DoStuff() { Debug.Log("Don't hate me..."); } }
Don’t panic, I won’t stay here long… I am just connecting the dots in the thoughts that got me where I ended up.
The Singleton may be one of the most well known and well used patterns. The purpose of the pattern is to make sure that “there can only be one” … yes, like Highlander.
Some go as far as calling Singleton an anti-pattern because of how often it is used, and then abused. I don’t personally have a problem with this pattern, and think pretty much everyone uses it far more often than they would like to admit. The reason for its popularity is probably the convenience that comes with a Singleton’s global access. In fact, I would not be surprised to find out that most Singletons are born with the desire for global access rather than for anything related to the number of instances. Global access is, I believe, a secret forbidden desire of all who write code.
How can we keep our easy access while avoiding tight-coupling to a concrete instance? How can we make our code testable? Can I remove the cons of this pattern while keeping the pros?
Phew, Dependency Injection
// Program to abstractions public interface ISomeDependency { void DoStuff(); } public class SomeFeature { // Usually pass a concrete instance of the dependency in a constructor public SomeFeature(ISomeDependency someDependency) { // Use the dependency in the class via the abstraction someDependency.DoStuff(); } }
Take a deep breath, you made it to familiar ground.
Pretty well any experienced developer I talk to will try to solve the dependency problem with a pattern called Dependency Injection. Even if a singleton lives in a project you may be encouraged to pass it as a dependency like this.
There are a lot of things I like about this pattern, but there are pain points I keep encountering:
- Large number of dependencies
- Daisy chained dependencies
- Dependencies that depend on each other
If you are trying to instantiate a class and find yourself having to inject more than a couple of dependencies, then in my opinion, something feels wrong. It’s even worse if the class constructor takes its own set of parameters that are necessary for its own configuration.
Daisy chained dependencies are often a culprit behind the first issue. The bigger issue, is that you are required to inject dependencies, not because the class needs them, but because something else needs them and the class merely needed to pass them along.
My final complaint is that sometimes you run into scenarios where dependencies depend on each other. This can make it difficult to stick to a consistent pattern, such as providing dependencies in a constructor.
Not to worry, many of these issues are resolved by the next pattern…
Huh? IoC Container
Inversion of control container is a pattern I have been using various versions of for a while. I wish more people used it. It allows you to obtain your dependencies from a container as needed, so that you can pass a single reference and can still get whatever dependency you need. You might even let the container be a Singleton and then you have easy global access while still being able to work with abstract dependencies. The code is now testable and solves most of the initial problems.
So why not stop here? There are still a few things I don’t like:
- The code now has a dependency on the IoC Container itself.
- There aren’t good ways to store dependencies as a collection.
The first issue, a dependency on the IoC container, isn’t that big of a deal breaker and honestly has some potential benefits as well. Depending on how you go about it, it could even lead to some new features, like letting different parts of an app have different sets of dependencies. Still, I kept wondering if it could be possible to remove the extra dependency entirely.
The second issue is the real complaint for me. I see basically two approaches for implementation:
- Explicitly list each dependency. This would allow you to have type safety and could avoid boxing, but I don’t consider it very maintainable because the container class would continue growing with every new app feature.
- Create a collection of dependencies. This probably looks like a collection of
object
that gets cast to the needed interface on demand. The downsides to this are a loss of type-safety and potentially boxing, such as if a dependency were implemented as a struct.
If anyone is aware of another option, that is easy to understand and performant, I would be curious to know. Otherwise, these issues ultimately led me to my new pattern.
Interface Injection
Interface injection is what I will name my pattern. It isn’t an official pattern to my knowledge. The basic idea is that I can use interface inheritance and generic interfaces to provide easy global access to an abstract dependency. It was inspired by IoC Container, in that I like being able to “Register”, “Resolve” and “Dispose” a dependency, I just wanted a different way to do it.
The pattern begins with a generic interface:
using System.Collections.Generic; using System; public interface IDependency<T> { // TODO: Add Code Here }
Later interfaces can inherit from the above interface so they will be able to share its functionality.
Now let’s begin fleshing out the rest of the IDependency
interface. The basic idea is that there are global functions that know how to “Resolve” and “Dispose” of the dependency itself. When you “Register” a dependency, you are really just providing the code that knows how to do that, so that the code can be run later when it is needed. Because the code lives in the interface of the dependency itself, any code that relies on the dependency already has everything it needs. Add the following:
static Func<T> _resolver; static Action<T> _disposer; public static void Register(Func<T> resolver) { _resolver = resolver; } public static void Register(Func<T> resolver, Action<T> disposer) { _resolver = resolver; _disposer = disposer; } public static T Resolve() { return _resolver(); } public static void Dispose(T entity) { _disposer(entity); }
For convenience, I will also provide a few more options for how to “Register” the resolver function. You can add these to the IDependency
interface, or may choose to add them in another child interface, whichever you prefer.
Usually my dependencies are in the form of systems, for which I only need one. If you currently use a Singleton, this type of dependency could replace it. A shared instance will be captured by a delegate and returned any time it is asked for.
public static void Register(T entity) { Register(delegate { return entity; }); }
Another scenario is where you want a dependency, but want a unique instance each time. This could be because you don’t want to share state, or maybe don’t have state at all. If you have a dependency that can be constructed with a parameterless constructor, then you could use the following variation:
public static void Register<U>() where U : T, new() { Register(delegate { return new U(); }); }
If you want to be able to pool and reuse dependencies, then here is another Register option which also showcases why you would use Dispose. Note that in this case the pool itself is captured by the functions:
public static void RegisterPool<U>() where U : T, new() { var pool = new Queue<T>(); Func<T> resolver = delegate () { if (pool.Count > 0) return pool.Dequeue(); return new U(); }; Action<T> disposer = delegate (T entity) { pool.Enqueue(entity); }; Register(resolver, disposer); }
Demo
Let’s take a look at how we would make use of the Interface Injection pattern. To start, any time you create a dependency, you would want it to inherit from IDependency
like so:
public interface IMyDependency : IDependency<IMyDependency> { public string Message { get; set; } }
Then a concrete dependency implements your interface like normal:
public class MyDependency: IMyDependency { public string Message { get; set; } public MyDependency() { Message = "Unknown"; } public MyDependency(string message) { Message = message; } }
Part of the initialization of your game or app may include some sort of Injector or Bootstrap object that actually registers the dependencies for your app. Following are different options you could take to Register MyDependency
for the IMyDependency
interface. You would pick the one that makes the most sense:
// Register by passing a custom Func<T> IMyDependency.Register(delegate { return new MyDependency("Foo"); }); // Register by passing a concrete conforming type IMyDependency.Register<MyDependency>(); // Register a preconfigured instance to share IMyDependency.Register(new MyDependency("Hello, world!")); // Register a type that will be pooled and reused IMyDependency.RegisterPool<MyDependency>();
Regardless of how the dependency was Registered, you will always obtain and use a dependency the same way. That would look like this:
var dependency = IMyDependency.Resolve(); Debug.Log(dependency.Message);
For pooled dependencies, you would return a dependency like this:
IMyDependency.Dispose(dependency);
Summary
In this lesson I discussed a very brief tour of some architectural patterns I have used in the past, along with various critiques of their use that led me to additional patterns. Since they all left something to be desired, I came up with a new pattern that I would like to use in my next project. If you like it, feel free to use it and let me know how it works for you.
If you find value in my blog, you can support its continued development by becoming my patron. Visit my Patreon page here. Thanks!
It’s nice to have you back! Do you have any plans for any new projects?
Oh thanks π I do actually have a goal – at the moment I am trying to create a sort of “core” for games based on the Pathfinder open game license.
https://pathfinder.d20srd.org/index.html
i know you keep saying no to this, but could you consider doing videos this time around?
That is a very good question. It would be a whole new skill to try to develop, and trying to create videos would almost certainly slow down the release of each lesson, but it might be worth trying out. So… maybe? I’ll try to be open to the idea of it at least.
Interesting! It seems like the locator pattern, but deconstructed by type– which adds the benefit of having a collection of instances.
Glad to hear π I like inspiring conversation in areas like this.
Hi,that is really nice!But I have a problem,if I have two class(maby MyDependencyA and MyDependencyB),how do I get what I really want?
This pattern is geared for problems where you only need a single concrete class conforming to an interface at any given time. It can even change what class is registered over time or just wait to register something dynamically based on a given context and still work. If you follow along with my D20 project series you will see examples of this in the future.
If you have a case where you need multiple different concrete classes conforming to the same interface at the same time, them you will need a different pattern such as dependency injection.
Thank you!I am following now,D20 is a new thing for me.