What is dependency injection?
IOC: Full name: Inversion of Control, Chinese name: controlled inversion, it also has a name called Dependency injection (Dependency injection, short di).
When an instance of a class requires the assistance of an instance of another class, the caller's instance is typically created in the traditional program design process. In the case of dependency injection, the creation of the callee's work is no longer done by the caller, so called control inversion, the work of creating the callee's instance is done by the IOC container, which is then injected into the caller, hence also known as dependency injection.
To give a simple example:
(1) In primitive society, there was hardly any social division of labor. The person who needs the axe (the caller) can only grind an axe (callee) on its own.
(2) Enter the industrial society, the factory appears. The axe is no longer done by ordinary people, but is produced in the factory, where the person needing the axe (the caller) finds the factory, buys the axe, and does not care about the manufacturing process of the axe.
(3) To enter the "On demand" society, people who need an axe do not need to find a factory, sitting at home to send a simple command: Need an axe. The axe appeared to him naturally.
In the first case, the caller of the instance creates the invoked instance, which inevitably requires the called class to appear in the caller's code. The loose coupling between the two cannot be achieved.
In the second case, the caller does not have to care about the specific implementation of the callee, only need to find an instance that conforms to a certain standard (interface) to be used. The code invoked at this point is interface-oriented programming, which allows the caller and callee to be decoupled, which is why the factory pattern is heavily used. But the caller needs to locate the factory itself, and the caller is coupled with the particular factory.
In the third case, the caller does not need to locate the factory by itself, and the dependent injection container automatically provides the callee instance when the program runs to the caller. In fact, both the caller and the callee are under the management of the dependency injection container, and the dependency between the two is provided by the dependency injection container. Therefore, the coupling between the caller and the callee is reduced further, which makes the application easier to maintain, which is the purpose of dependency injection.
Using PHP to implement a lightweight dependency injection container
Let's start by creating a class that looks something like this:
_service[$name] = $definition; } Public function Get ($name) { if (isset ($this->_service[$name])) { $definition = $this->service[$ Name]; } else { throw new Exception ("Service"). Name. "' wasn ' t found in the Dependency Injection container"); } if (Is_object ($definition)) { $instance = Call_user_func ($definition); } return $instance; }}
Now we have a simple class that contains a property and two methods. Assuming we now have two classes, REDISDB and CACHE,REDISDB provide a Redis database operation, and the cache is responsible for caching the implementation of the functionality and relies on REDISDB.
Class redisdb{ protected $_di; protected $_options; Public function __construct ($options = null) { $this->_options = $options; } Public Function Setdi ($di) { $this->_di = $di; } Public function Find ($key, $lifetime) { //code } public function Save ($key, $value, $lifetime) { //code } public function Delete ($key) { //code }}
In this class we have simply implemented Redis query, save and delete. You may have questions, and another way to SETDI is to do something. Wait until I continue to explain to you. The other class is similar to the current class structure:
Class cache{protected $_di; protected $_options; protected $_connect; Public function __construct ($options = null) {$this->_options = $options; The Public Function Setdi ($di) {$this->_di = $di; } protected function _connect () {$options = $this->_options; if (Isset ($options [' Connect ')]) {$service = $options [' Connect ']; } else {$service = ' redis '; } return $this->_di->get ($service); Public function Get ($key, $lifetime) {$connect = $this->_connect; if (!is_object ($connect)) {$connect = $this->_connect () $this->_connect = $connect; }//code ... return $connect->find ($key, $lifetime); Public function Save ($key, $value, $lifetime) {$connect = $this->_connect; if (!is_object ($connect)) {$connect = $this->_connect () $this->_conNect = $connect; }//code ... return $connect->save ($key, $lifetime); Public Function Delete ($key) {$connect = $this->_connect; if (!is_object ($connect)) {$connect = $this->_connect () $this->_connect = $connect; }//code ... $connect->delete ($key, $lifetime); }}
Now that we have implemented the REDISDB and the cache of the two components, the specifics of the details are not discussed here first, to see how to use it. First, you need to inject two components into a container:
Set (' Redis ', function () { return new redisdb ([ ' host ' = ' 127.0.0.1 ', ' port ' = 6379 ]) ; $di->set (' Cache ', function () use ($di) { $cache = new Cache ([ ' Connect ' = ' Redis ' ]); $cache->setdi ($di); return $cache; }); Then in any place where you want to use the cache $cache = $di->get (' cache '); $cache->get (' key '); Get cached Data $cache->save (' key ', ' value ', ' lifetime ');//Save Data $cache->delete (' key ');//Delete data
You may find it a bit tedious to get here. The cache and REDISDB are so structured that they can write redis to the cache without having to separate them? But you think about it, some data timeliness is not so high and the number is large, with redis a bit inappropriate, MongoDB is a better choice; some data update frequency is slower, the query speed is not required, write files directly to the hard disk may be more appropriate; Your customers think Redis operation is a bit difficult, let you change him to memcache ... That's why it was separated. Then, continue to improve the code:
Interface Backendinterface {public function find ($key, $lifetime); Public function Save ($key, $value, $lifetime); Public Function Delete ($key);} Class Redisdb implements backendinterface{Public function find ($key, $lifetime) {} Public Function Save ($key, $val UE, $lifetime) {} Public Function Delete ($key) {}}class MongoDB implements backendinterface{public function find ( $key, $lifetime) {} Public Function Save ($key, $value, $lifetime) {} Public Function Delete ($key) {}}class file I Mplements backendinterface{Public Function Find ($key, $lifetime) {} Public Function Save ($key, $value, $lifetime) {} public Function Delete ($key) {}} $di = new Di ();//Redis$di->set (' Redis ', function () {return new Redisdb ([ ' Host ' = ' 127.0.0.1 ', ' port ' = 6379]); /Mongodb$di->set (' MONGO ', function () {return new MongoDB [' host ' = ' 127.0.0.1 ', ' port ' = = 12707]); /file$di->set (' file ', function () {return new file ([' path ' = ' path ');}); /Save at Redis$di->set (' Fastcache ', function () use ($di) {$cache = new cache ([' Connect ' = ' Redis ') ]); $cache->setdi ($DI); return $cache;}); /Save at Mongodb$di->set (' Cache ', function () use ($di) {$cache = new cache ([' Connect ' = ' MONGO ') ]); $cache->setdi ($DI); return $cache;}); /Save at File$di->set (' Slowcache ', function () use ($di) {$cache = new cache ([' Connect ' = ' file ') ]); $cache->setdi ($DI); return $cache;}); /Then in any place where you want to use the cache $cache = $di->get (' cache ');
We have added a new interface Backendinterface, which stipulates that the Redisdb,mongodb,file these three classes must implement the functions required by this interface, as for the other icing on the cake function, as you play. The cache code does not seem to change because the cache does not need to be concerned about how the data is stored in the database or file. The caller of the cache does not need to be concerned about how the cache is implemented, as long as the corresponding method is implemented according to the interface. Multi-person collaboration you will benefit more, you just have to agree on a good interface, and then separate implementation on the line.
This is where the charm of dependency injection is, though it seems so simple.
The above code can continue to improve until you think it is impeccable. For example, the Redis service is called multiple times in a request, and each call is recreated, which can compromise performance. Just extend the Di container to add a parameter or how to add a method to you.
Class di{ protected $_service = []; protected $_sharedservice = []; Public function set ($name, $definition, $shared = False) { if ($shared) { $this->_sharedservice[$name] = $definition; } else { $this->_service[$name] = $definition; } } Public function Get ($name) { if (isset ($this->_service[$name])) { $definition = $this->service[$name] ; } else if ($this->_sharedservice[$name]) { $definition = $this->_sharedservice[$name]; } else { throw new Exception ("Service"). Name. "' wasn ' t found in the Dependency Injection container"); } ... }
Thus, if a service is to be called multiple times in a single request, you can set the shared property to true to reduce unnecessary waste. If you think it's a bit tedious to setdi every time you inject it, and want to get him to SETDI automatically, you can do this:
Interface diawareinterface{public function Setdi ($di); Public function Getdi ();} Class di{ protected $service; Public function set ($name, $definition) { $this->service[$name] = $definition; } Public function Get ($name) { ... if (Is_object ($definition)) { $instance = Call_user_func ($definition); } If the Diawareinterface interface is implemented, automatically injects if ( is_object ($instance)) { if ($instance instanceof diawareinterface) { c16/> $instance->setdi ($this); } } return $instance; }} Class Redisdb implements Backendinterface, diawareinterface{public function Find ($key, $lifetime) {} Public function Save ($key, $value, $lifetime) {} public function Delete ($key) {}}
Then, you can do this:
$di->set (' Cache ', function () { return new cache ([ ' Connect ' = ' mongo ' );});
The DI container we are now implementing is still rudimentary and does not support complex injections, and you can continue to refine it.
However, by using these codes you already know what dependencies are injected into, and you can apply this idea to your project, or start developing your own framework. If you want to continue in-depth study, please click Scene.
Finish.