Architecture Evolution Learning Reflections (4)
Learning Awareness of IOC [1]
IOC Related Concepts Awareness
What is IOC?
The full name of IOC is Inversion Of Control. It is a control idea that can be explained as the dependencies between classes and classes are no longer directly controlled by the code, but through the container to control and configure the realization.
Control reversal? So what's a forward pass? What's the benefit of reversal? What's an IOC anyway?
Okay, let's start to recognize and understand step by step~
Since it's an idea, let's start with its common implementation, DI.
DI
DI, or Dependence Injection, which is an implementation of IOC.
The concept of dependency, which we explained in the first article, is an object/reference holding relationship.
The simplest single dependency:
A creates a dependency on B.
public class A
{
public B b = new B();
}
And what is injection?
Injection is the process of establishing dependencies.
public class A
{
// hold a null reference to B
public B b =null;
}
public class B {}
void Main()
{
var b = new B();
// Create a dependency between AB
//i.e. inject
= b; // i.e. inject
}
In other words, the process by which A and B establish a dependency is injection. Because the injection operation. So that the b reference in A is no longer null, but directly to get a reference to the B object.
At this point we recognize the operation of "dependency injection", which is to help establish object-to-object dependencies, so that object A can complete the operation of holding a reference to object B.
Use of IOCContainer
The concrete implementation of DI cannot be separated from the DI container, which is sometimes called the IOC container, through which the work of dependency injection is accomplished. Take a look at the use of IOCContainer.
using ;using ;using ;using ;using ;using ;using ;using
using ;
using UnityEngine ; using
using QFramework
namespace IOCContainerExample
{
public class A
public void Say()
public void Say()
{
("I am A" + ()); }
}
}
public class IOCExample: MonoBehaviour
{
// Add the injection token
[Inject]
public A a {get;set;}
void Start()
{
// Create the container instance
var container = new QFrameworkContainer();
// Register the type to be injected
<A>().
// Perform dependency injection
// will automatically find the object of Inject Atrribute
(this);
//It's ready to use after injection
().
}
}
}
To use IOCContainer for dependency injection, first register the container with the relevant type, then mark the null reference of the object to be acquired, and then call the container's Inject method to complete the dependency injection.
This container can be interpreted as a "rental agent", landlords who want to rent a house to the agent for "Register" registration, when the call Inject () method, the agent will be based on the tenant ( [Inject] markers When the Inject() method is called, the intermediary will match the tenant ([Inject] tagged null reference) with a suitable listing based on the tenant's rental type, accomplishing the tenant's purpose of looking for a listing, and helping the tenant to complete the dependency on the house.
This is a simple use of IOCContainer.
In general, DIContainer provides the following API:
- Registration type: Register<TSource,TTarget>.
- Injection: Inject (object obj)
- Ans: Resolve
()
Registration and injection we use in the above content to understand, and resolve (Resolve) what does it mean?
Resolve actually returns instances based on type.
That raises the question, is the returned instance newly created each time or is it the same instance?
In the following content will be analyzed step by step.
Let's continue to talk about the responsibilities of the IOCContainer, which, as compared above, are managing dependencies and injecting dependencies. Dependency management is done through class types and dependency injection is done by assigning values to null references. In other words, a rental agent helps to establish a relationship between a tenant who wants to find a property and a registered property, which is manifested in the creation of a dependency.
This intermediary holds the correspondence between the tenant and the listing, and can also perform management duties on this dependency. So a typical IOCContainer will use a Dictionary<Type,object> as the core data structure.
That is, you can get instances of Type based on Type, and dependency is such a thing in code. And this dependency does not mean that Type and object depend on each other, but that their
It's a bit more roundabout here, which means that the key-value pairs stored in the data structure here are dependencies on the elements themselves, dependencies on obj in Inject (object obj).
I.e., the object class has a dependency on this pairing information for the null indexes that are expected to be injected. The analogy used to be that the "tenant list" (object) has a dependency on the agent's matching information, because the tenant's rank (object) in the matching information is the only thing that allows them to successfully match with the properties they want.
I don't know if you're getting it right here, but this is how dependencies are managed in IOCContainer. This is how dependencies are managed in IOCContainer.
That's okay, we'll look at specific examples below.
Let's chat briefly about the power of IOCContainer before we look at the examples.
The power of IOCContainer
People who have used the singleton will have this realization: use the singleton for a moment, always use the singleton a straight!
Because the singleton is fetched as it is used, interaction with other modules becomes very easy.
This is really convenient when the project is small and you don't have to deal with all the dependencies individually. As long as everyone follows the agreed upon hierarchy to access the proxy module.
But if the project has many features and multiple module hierarchies, theIt is also difficult to continue to guarantee the content of the agreementAnd from a technical constraints point of view alone, there is no restriction on single instance access, like it's open for use with all hierarchical modules. This doesDifficult to reflect the hierarchical relationship between modules, clearly not conducive to the architectural design of the entire project.
So how do you use singletons wisely and avoid breaking the hierarchy?
The solution is very simple, that is, the topmost module are used in a single case, and then some of the bottom module, as a member of the top module variables, so as to achieve the logical layer can not directly access the bottom module, but must be indirectly through the top module to use the bottom module's services
This solves the problem of not being able to express hierarchy in a singleton structure, but at the same time it loses all the benefits of singletons
, but also loses all the benefits of singleton, easy to extend.
Because the single instance result now creates a dependency on the underlying module, the dependency on the underlying module should be taken into account when expanding the functional module.
Then the power of IOCContainer comes through to help manage dependencies.
Dependency management
Let's go back and look at dependency management related content, where the timing and placement of dependency injection is an issue of concern.
public class ModuleA
{
public ModuleB moduleB;
}
public class ModuleB
{
}
To see a dependency to be injected, dependency injection can be done inside ModuleA:
public class ModuleA
{
public ModuleB moduleB = new ModuleB();
}
But what if MoudleB is public? Creating it internally would obviously not be appropriate, since this is a module, not simply an object.
Then create the object externally:
void main()
{
var moduleA = new ModuleA;
= new MoudleB();
}
So, if a dependency is injected externally and used inside a module, you need to know if the dependency is injected or not?
Where is it injected? Can I use it directly?
public class ModuleA
public class ModuleA; {
public ModuleB moduleB.
/*.
.... Other code logic
*/
void someFunc()
{
// Need to use moduleB
// Need to know if moduleB has a value or not? Where is it fetched?
}
}
So this brings us to the dependency creation process.
And using a singleton, that doesn't have this problem.
public class ModuleA
{
void someFunc()
{
// Use the singleton directly
().
}
}
Now it's time for IOCContainer to make an appearance.
Yes, IOCContainer's responsibility is to inject dependencies and manage them.
Managing dependencies with IOCContainer
public class ModuleA
{
[Inject]
public ModuleB moduleB.
public class ModuleA { [Inject] public ModuleB; moduleB; void something()
{
// Use it without worrying about whether it's null or not.
().
}
}
Uniform registration of dependencies when starting a program:
public static QFrameworkContainer Container {get; set;}
void Main()
{
Container = new Container();
<MoudleB>();
}
Inject dependencies in the constructor of MoudleA:
public class ModuleA
{
[Inject]
public ModuleB moduleB.
// Constructor
public MoudleA()
{
//Inject the dependency
(this);
}
void something()
{
// Use it without worrying about whether it's null or not.
(); }
}
}
This makes it much clearer to use IOCContainer to manage the various dependencies on its modules:
Feel free to use dependent content, and just leave the dependency management and injection to IOCContainer.