This is the 14th day of my participation in Gwen Challenge
About Dependency Injection
What is dependency injection? What does he have to do with inversion of control?
I think it’s appropriate to put it here after seeing a reply from a Zhihu user:
In simple terms, A depends on B, but A does not control the creation and destruction of B, and only uses B, so the control of B is handed over to a, which is called inversion of control (IOC).
If a relies on B, it must use B’s instance, so it passes IN B through A’s interface. Passing in B through the construction of A; Pass in B by setting a’s property; This process is called dependency injection (DI).
In fact, inversion of control is a design principle in object-oriented programming, and dependency injection is one way to implement it. There is another way to implement this called dependency lookup, which I won’t cover here because it is not relevant to this article.
Di actually consists of two things: a dependency injection pattern (also known as inversion of control) and a framework for implementing dependency injection.
Why use DI?
Dependency injection separates the transitionalization of an object from the actual logic and behavior encapsulated.
This model has many benefits. Such as:
Explicit dependencies
All dependencies are passed as construction parameters, and it’s easy to understand how a particular object depends on the rest of the environment.
Code reuse
Such an object is easier to reuse in other environments because it is not tied to the implementation of a particular dependency.
Easy to test
Because testing is essentially about making an object live in the absence of other environments.
It is possible to follow the dependency injection paradigm without any framework.
But if you follow the dependency injection pattern in this case, you usually have to write a nasty main() method where you instantiate all the objects and wire them together.
The dependency injection framework frees you from this boilerplate. It ensures that reference associations between applications are declarative rather than imperative. Each component declares its dependencies, and the framework does address these dependencies transitively…
node-di
There are many libraries that implement dependency injection, and this article uses Node-di as an example to show the practical details.
This is an Angular.js influenced DI library available for Node.js. It’s still getting millions of downloads a week.
Although the author states that maintenance has been stopped, details about dependency injection can be learned from its implementation.
First take a look at the code repository
Github.com/vojtajina/n…
We can see the following four files:
The title | use |
---|---|
annotation.js | annotations |
index.js | File entry |
injector.js | injector |
module.js | The module |
The index.js task is very simple, mainly to export the other three files.
Let’s look at a couple of other files. This can also be seen in conjunction with the library example
As an example, the DI framework was first introduced and an Injector was directly generated, and a set of initialization recipes were passed into the Injector’s parameters. Finally, invoke the injector’s invoke method to execute.
Injector.js is the core of the library. Here’s a look at the other files:
module.js
Module. js maintains a providers array. Provider supports factory, Value, and Type, which represent returned methods, values, and objects. The previous three methods put values of the corresponding types in arrays. The following forEach method iterates through the providers array.
The return this here is the key to making sure that you can chain the call.
annotation.js
And annotation. Js has two methods annotate and parse
Annotate copies an array, extracts the last parameter separately, sets the array as the $Inject attribute of the last parameter, and returns the last parameter.
As those familiar with early Angular.js might imagine, this might fit the following pattern:
angular.controller(['$scope'.'$timeout'.function($scope, $timeout){})Copy the code
Does it matter? I’m going to press the button here.
The parse method is interesting. Actually returns a list of arguments to a function.
Let’s say I have a function that takes the sum of two numbers, and two of the parameters are the parameters of the sum.
function fn(a, b){
return a + b;
}
Copy the code
First determine if the argument passed is a function, and if so, convert the function to a string, then parse out the argument list using a regular expression
var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
Copy the code
This regular expression can match functions in either of the following formats
// /* (((this is fine))) */ function(a, b) {}
// function abc(a) { /* (((this is fine))) */}
Copy the code
The result is:
The string is then sliced and whitespace removed, and a list of keys is returned.
injector.js
More than 200 lines. Look at the main part first. Injector initializes the Injector by passing in an array of Modules. This array will be looped through, storing the values of each Module in the top providers and preserving them in the current object.
As mentioned above, there are three types of Module: Factory, type and value. The corresponding transformation method is performed as a factoryMap.
Injector initializes the second parameter as parent, which forms the Injector dependency tree.
Invoke invokes methods, first looking for the list of dependent tokens, and then loops through the GET method to convert to the real dependency. Finally, the real dependency is passed in when the FN method is called.
AutoAnnotate here is the parse function in annotation. For details, see above.
The get method in Dependencies is at the heart of getting the real dependencies.
Get the actual dependencies from the providers object first. For tokens that contain delimiters, recurse to find the actual dependencies.
If not, get it from the current Injector.
Not available from the parent Injector instance.
And then there’s the loop dependent judgment. If the provider cannot be found, the following methods are executed to display an error message indicating that the provider is not found.
conclusion
In fact, the resolution process of dependency injection is actually the process of obtaining dependencies based on the Provider token. To obtain a dependency, the provider first looks for the value explicitly specified by the provider. If it does not find a dependency, the provider looks for the dependency from the current instance to the parent instance. Cyclic dependencies can occur in this case. Special handling is required for loop dependencies.
When the provider list is specified, it is specifically divided into factory, Value, and Type. These three types represent return methods, values, and objects, respectively. There are three different types of internal processing.
I hope I can help you.