All of the systems mentioned above are frameworks. While I’ve provided examples specific to Unity, we could also include native C# frameworks like AutoFac, but these are systems adapted and optimized specifically for Unity.
No framework is written to add extra cost or difficulty to a project, but they can become outdated or fall behind modern systems if their developers stop supporting them.
What Are They Used For?
The purpose of these frameworks is actually completely independent of and unrelated to the application of SOLID principles.
To solve the multiple instance problem we encountered with Singletons, we could develop a custom plugin. Solving that problem in that specific scenario doesn’t mean we’ve entirely eliminated the issue. If we develop a comprehensive plugin that eliminates this fundamental problem with Singletons, it becomes a framework, and there’s no need to calculate a benefit/cost ratio.
Zenject and VContainer are frameworks that have anticipated and tested potential plugin development scenarios in situations like Scalable Multiple Instances, Interface Segregation, and Dependency Inversion, and have provided solutions in this context.
A Simple Example
First, let’s create an interface to log our operations by applying the Interface Segregation principle.
(Assume there are different types of Logger structures in the Editor and Build screens, and we use an interface for this purpose.)
interface ILogger { void Log(string message); }
Let’s create an
EditorLogger
using theILogger
interface and implement our method.public class EditorLogger : ILogger { public void Log(string message) { Debug.Log(message); } }
Let’s inherit a class with an instance tied to the
ILogger
interface from the constructor of ourRunner
class and store and use it in a private variable.public class Runner { private ILogger _logger; public Runner(ILogger logger) { _logger = logger; } public void Run() { _logger.Log(“Message to be logged”); } }
The code you see below is a Dependency Injection (yes, really):
public class GameController { public GameController() { var logger = new EditorLogger(); // We created a “dependency”. var runner = new Runner(logger); // We performed an “injection”. runner.Run(); } }
When we call our
GameController
class, we need to produce 2 dependent classes (dependencies).Now, imagine we are using this logic in a large project. We might need to pass these inheritances through constructors according to the dependencies between dozens of different classes. However, we can’t use principles like Singleton because
ILogger
is an interface and can have different types of instances.Here’s how we can solve our problem with VContainer:
First, we define an address for all our instances and provide an entry point for our project. Here,
GameController
itself will be an entry point.public class GameLifetimeScope : LifetimeScope { protected override void Configure(IContainerBuilder builder) { builder.Register<ILogger>(Lifetime.Scope); // We registered to our interface. builder.Register<Runner>(Lifetime.Scope); builder.RegisterEntryPoint<GameController>(); } }
Our new
GameController
class will look like this:public class GameController { public GameController(Runner runner) { // VContainer handled all dependencies and injections. runner.Run(); } }
VContainer will create and set the
ILogger
instance required for theRunner
class’s constructor. Inside theGameController
, without using anynew
statements, we’ll receive the instances that VContainer created for us in our constructor.
Dependency Injection has solutions not just in this example but in multiple areas. I’ll try to touch on them as the need arises.
Reply by Email