Theoretical discovery Problems
Before going into detail, we need to make sure that we understand what "IOC control inversion" and "Di Dependency injection" are and what problems can be solved, which are clearly explained in Wikipedia.
- controlling inversion (inversion of control, abbreviation IoC): It is a design principle in object-oriented programming, which can be used to reduce the coupling degree between computer code.
- Dependency Injection (Dependency injection, short di): Di is an implementation of the IOC, represented by the creation of a dependent B object during instance creation of Class A, by type or name to determine the injection of different objects into different properties.
- Dependent Lookups ( Dependency Lookup, DL):DL is another implementation of the IOC, when needed, by invoking the method provided by the framework to obtain the object, obtain the need to provide the relevant configuration file path, key and other information to determine the acquisition object
Dependency Injection and Dependency lookup are 2 implementations of control inversion, which is seldom seen, and we mainly study dependency injection.
If you have not touched these concepts before, it may be too abstract to understand, but the following scenario you should have seen:
Because most applications implement business logic by cooperating with each other by two or more classes, each object needs to get a reference to the object it is working with (that is, the object it depends on). If this acquisition process is implemented on its own, this will result in highly coupled code and difficult to maintain and debug.
In other words: "Class A uses the Class B object B, in general, you need to explicitly type a B object in the code of a", which causes if a wants to replace B with a better implementation version of A + B, you need to modify the code explicit new B + object.
The traditional approach to solving this problem is generally to extract a INTERFACEOFB interface for B and C +, and then let Class A rely only on INTERFACEOFB, and ultimately the caller of Class A will decide whether to pass in a B-plus + object. Modifying the caller code and modifying the Class A code has no fundamental change for us, is there a better way?
Solution Ideas
Finally, lazy programmers are bored with the way this code is developed: because we control the generation of class B objects in the code, which leads to code coupling, even if the Class A relies on INTERFACEOFB, or if it is going to write code like new B () or new + + () somewhere in the program, how to hack it?
The answer is: the Generation of Class B objects to a separate object generator to be responsible, then class A only need to rely on the object generator, and as to whether it is to generate B-plus object, it is the behavior inside the object generator, so that A and B decoupling open, this is called "inversion of Control", Control is given to the object builder.
It is not possible to throw a problem to the object generator, because the object generator also faces a hard-coded problem with new B or new + +, so you must give the object generator a power:
- This is described in the Object Builder's configuration file: {"INTERFACEOFB": "Class B +"}, indicating that the INTERFACEOFB interface should instantiate a B + object.
- A class constructor has a INTERFACEOFB entry, for example: function __construct (interfaceofb obj).
- Call the Object Builder (DI) to get A Class A object, Di->get ("Class A"). The object Builder uses reflection to analyze the constructor of Class A, and after discovering the INTERFACEOFB parameter, the new B + () object is passed to the constructor of a as described in the previous configuration file, resulting in a object.
Summary of the above process is: the object generator through the reflection mechanism of a class of the constructor dependency, and based on the relationship in the configuration to generate a dependent object instance passed to the constructor, finally complete the Class A object creation.
The above process is the main implementation of "Dependency injection", the object generator we usually become "DI Container", that is, "dependency injection container."
It is important to note that the constructors of B or + + can rely on INTERFACEOFC, so the analysis of the entire dependency is recursive.
Practice
When we talk about ' di dependency injection ', we know very well that Di is dependent on the constructor, but it is easy to ignore the source of the information {"INTERFACEOFB": "Class B +"}. If di does not know this information, then in the analysis of the constructor is not possible to know what the interface interfaceofb should be corresponding to the object, this information in the DI implementation is generally through the set method is actively set to the Di container dependency, Of course, this information storage media can be a configuration file or hard-coded incoming.
Take PHP's Yii2.0 framework as an example to see what the core idea is when it comes to Di, not too thin, but the ideas and concepts mentioned above will be reflected.
Set setting class definition
Public function set ($class, $definition = [], array $params = []) { $this->_definitions[$class] = $this->normali Zedefinition ($class, $definition); $this->_params[$class] = $params; unset ($this->_singletons[$class]); return $this;}
This is the Setup interface that mentions {"INTERFACEOFB": "Class B +"}, for example:
$container->set (' yii\mail\mailinterface ', ' Yii\swiftmailer\mailer ');
This means that if a dependent mailinterface is encountered, then constructs a Mailer object to it, the params is used to pass to the mailer::__construct the construction parameter, previously mentioned the dependency analysis is recursive, The construction of the Mailter object is also di responsible (not the simple new out), once you pass the construction parameters to mailer, then di will not reflect the analysis of mailter dependence, directly into the params can be a new mailer out.
Get generated class object
Public function Get ($class, $params = [], $config = []) {if (Isset ($this->_singletons[$class])) { Singleton//is already get past and set as Singleton, then return a singleton object can be returned $this->_singletons[$class]; } elseif (!isset ($this->_definitions[$class])) {//non-singleton needs to generate new objects, but previously there was no set over-class definition,//So only direct reflection of the analysis constructor's dependent Lai return $this->build ($class, $params, $config); }//The class definition has been set earlier, and the class is defined more specifically to help us construct the object more quickly $definition = $this->_definitions[$class]; The class definition can be a function that generates an object directly for Di if (Is_callable ($definition, True)) {//merges the construction parameters set by the set with this incoming construction parameter Then analyze whether these incoming construction parameters are arguments (for example: int,string), because yii allow//params is a instance object, which represents another class definition (it points internally to a definition in the DI container) In order for this constructor parameter to be passed to the current constructor, a recursive call to Di->get is required to create it as an argument. $params = $this->resolvedependencies ($this->mergeparams ($class, $params)); This is the function of the allocation object, if the construction parameters need to ensure that all arguments are $object = Call_user_Func ($definition, $this, $params, $config); } elseif (Is_array ($definition)) {//Common class definition $concrete = $definition [' class ']; unset ($definition [' class ']); Merge the set config with this incoming config $config = Array_merge ($definition, $config); Combine the params construction parameter set with this incoming constructor parameter $params = $this->mergeparams ($class, $params); Here: $class represents Mailinterface, and $concrete represents mailer if ($concrete = = = $class) {//This is a recursive exit, generating a target Class object is available, there is nothing to study $object = $this->build ($class, $params, $config); } else {///Obviously, here to construct mailinterface is equivalent to to construct Mailer object $object = $this->get ($concrete, $params, $config); }} elseif (Is_object ($definition)) {return $this->_singletons[$class] = $definition; } else {throw new Invalidconfigexception ("Unexpected object definition type:".) GetType ($definition)); } if (Array_key_exists ($class, $this->_singletons)) {//singleton $this->_singletons[ $class] = $object; } return $object; }
The implementation of the idea in the previous analysis is very clear, it is not difficult to understand. This function specifies the class to be allocated by class, and the params specifies the construction parameters, as in the previous set principle: If the construction parameters are complete, no analysis is required. (The final config is an extra property to inject into the object, which is the Yii2 attribute, not the focus).
What do you need to do when building a Build object? is to get the classes that the constructor relies on based on the reflection mechanism, and then use the params parameter directly if the params passes in the constructor parameter, and if not specified, the recursive di->get () is required to generate the arguments and eventually the object is generated through the constructor.
protected function Build ($class, $params, $config) {/* @var $reflection Reflectionclass *///Use reflection to analyze class structure The parameters of the constructor,//Where the return value reflection is the reflection object of class,//dependencies is all parameters of the constructor, there are several cases://1, parameters have default values, directly with// 2, no default value, and not a non-class int, then return instance point to the corresponding class, wait for the following recursive get list ($reflection, $dependencies) = $this->getdepende Ncies ($class); The passed-in constructor parameter has the highest precedence, overriding the construction parameters of the front reflection analysis, foreach ($params as $index + $param) {$dependencies [$index] = $pa Ram }//Complete checking of parameters, if the dependency is instance to class, then the recursive di->get gets the instance//if it is a instance that specifies Int,string, then the caller does not have the params pass in the value, constructs The function default parameter also has no value,//must throw exception//If it is not instance, the argument is that the params user passed in arguments can be directly used $dependencies = $this->resolvedependenc IES ($dependencies, $reflection); if (empty ($config)) {return $reflection->newinstanceargs ($dependencies); }//Finally through the reflection object, pass in all construction arguments, complete the object creation if (!empty ($dependencies) && $reflection->imPlementsinterface (' yii\base\configurable ')) {//set $config as the last parameter (existing one'll be OVERWR Itten) $dependencies [Count ($dependencies)-1] = $config; Return $reflection->newinstanceargs ($dependencies); } else {$object = $reflection->newinstanceargs ($dependencies); foreach ($config as $name = + $value) {$object $name = $value; } return $object; } }
If you are interested, you can look at the getdependencies and Resolvedependencies implementations, which cache the reflection information for each class (reflection is very expensive), which embodies the use of instance: Represents a class object that has not yet been instantiated. Need to get di->get.
protected function Getdependencies ($class) {if (Isset ($this->_reflections[$class]) {return [$ this->_reflections[$class], $this->_dependencies[$class]]; } $dependencies = []; $reflection = new Reflectionclass ($class); $constructor = $reflection->getconstructor (); if ($constructor!== null) {foreach ($constructor->getparameters () as $param) {if ($param-& Gt;isdefaultvalueavailable ()) {$dependencies [] = $param->getdefaultvalue (); } else {$c = $param->getclass (); $dependencies [] = instance::of ($c = = = null? NULL: $c->getname ()); }}} $this->_reflections[$class] = $reflection; $this->_dependencies[$class] = $dependencies; return [$reflection, $dependencies]; } protected function Resolvedependencies ($dependencies, $reflection = null) { foreach ($dependencies as $index = + $dependency) {if ($dependency instanceof Instance) { if ($dependency->id!== null) {$dependencies [$index] = $this->get ($dependency->id); } elseif ($reflection!== null) {$name = $reflection->getconstructor ()->getparameters () [$ Index]->getname (); $class = $reflection->getname (); throw new Invalidconfigexception ("Missing required parameter \" $name \ "when instantiating \" $class \ ".");} }} return $dependencies; }
At last
Finally, I would like to say a little bit about Yii2 's Serviceloader, which is based on Di container, which encapsulates a layer, maintains the component component Singleton in Serviceloader, and component's generation is implemented via DI container.
It is interesting, however, that the implementation of Serviceloader does not fully use Di container's construction dependency injection capability, just the class completion of the incoming Component object creation, and finally injected a few config-specified properties, There is no ability to control the params, this can be seen in the Serviceloader set and get methods, however this design basically requires that the component constructor parameters should be able to be independently constructed without external intervention (intervention refers to the Di->set class definition).
In addition to Serviceloader, the entire yii2.0 framework did not find the ability to call Di->set for class definitions through the configuration file automation, which is the way to go backwards, This basically leads to the yii2.0 's ability to apply the DI to the Serviceloader level, and the recursive parsing relies on the path of no structural parameter or default parameter construction.
It is in this context that yii2.0 's "dependency injection" is also basically degenerate to serviceloader get nested get, which is similar to the "dependency lookup" concept: Write the component configuration of a and B respectively in the configuration, and configure a compoenent dependent B Component, and then through Serviceloader get a component,a class inside from the configuration to remove the dependent component (that is, b), and finally through the Serviceloader to get B component.
Talk about the IOC control inversion in PHP, di dependency Injection