-
php 依赖注入 和 控制反转 php设计模式
2018-09-18 10:47:31IOC:英文全称:Inversion of Control,中文名称:控制反转,它还有个名字叫依赖注入(Dependency Injection,简称DI)。 当一个类的实例需要另一个类的实例协助时,在传统的程序设计过程中,通常由调用者来创建被...转载自 :https://blog.csdn.net/lijingshan34/article/details/71526118
什么是依赖注入?
IOC:英文全称:Inversion of Control,中文名称:控制反转,它还有个名字叫依赖注入(Dependency Injection,简称DI)。
当一个类的实例需要另一个类的实例协助时,在传统的程序设计过程中,通常由调用者来创建被调用者的实例。而采用依赖注入的方式,创建被调用者的工作不再由调用者来完成,因此叫控制反转,创建被调用者的实例的工作由IOC容器来完成,然后注入调用者,因此也称为依赖注入。
举个简单的例子:
(1)原始社会里,几乎没有社会分工。需要斧子的人(调用者)只能自己去磨一把斧子(被调用者)。
(2)进入工业社会,工厂出现。斧子不再由普通人完成,而在工厂里被生产出来,此时需要斧子的人(调用者)找到工厂,购买斧子,无须关心斧子的制造过程。
(3)进入“按需分配”社会,需要斧子的人不需要找到工厂,坐在家里发出一个简单指令:需要斧子。斧子就自然出现在他面前。
第一种情况下,实例的调用者创建被调用的实例,必然要求被调用的类出现在调用者的代码里。无法实现二者之间的松耦合。
第二种情况下,调用者无须关心被调用者具体实现过程,只需要找到符合某种标准(接口)的实例,即可使用。此时调用的代码面向接口编程,可以让调用者和被调用者解耦,这也是工厂模式大量使用的原因。但调用者需要自己定位工厂,调用者与特定工厂耦合在一起。
第三种情况下,调用者无须自己定位工厂,程序运行到需要被调用者时,依赖注入容器自动提供被调用者实例。事实上,调用者和被调用者都处于依赖注入容器的管理下,二者之间的依赖关系由依赖注入容器提供。因此调用者与被调用者的耦合度进一步降低,这使得应用更加容易维护,这就是依赖注入所要达到的目的。
用php实现一个轻量的依赖注入容器
首先我们创建一个类,看起来是这样的:
<?php class Di { protected $_service = []; public function set($name, $definition) { $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; } }
现在我们已经有了一个简单的类,包含一个属性和两个方法。假设我们现在有两个类,redisDB和cache,redisDB提供一个redis数据库的操作,cache负责缓存功能的实现并且依赖于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 } }
在这个类中我们简单实现了redis的查询、保存和删除。你可能会有疑问,另外一个方法setDi是做什么的。待我继续为你讲解。另一个类和当前这个类结构很像:
class cache { protected $_di; protected $_options; protected $_connect; public function __construct($options = null) { $this->_options = $options; } 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); } }
现在我们就当已经实现了redisDB和cache这两个组件,具体的细节这里就先不做讨论了,来看看如何使用使用吧。首先需要将两个组件注入到容器中:
<?php $di = new Di(); $di->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; }); // 然后在任何你想使用cache的地方 $cache = $di->get('cache'); $cache->get('key'); // 获取缓存数据 $cache->save('key', 'value', 'lifetime'); // 保存数据 $cache->delete('key'); // 删除数据
到这里你可能会觉得这样以来反而有点繁琐了。cache和redisDB的结构如此之像,完全可以把redis写到cache中而没必要单独分离出来?但是你想过没有,有些数据及时性没那么高而且数量比较大,用redis有点不合适,mongodb是更好的选择;有些数据更新频率更慢,对查询速度也没要求,直接写入文件保存到硬盘可能更为合适;再或者,你的客户觉得redis运维难度有点大,让你给他换成memcache… 这就是为什么把它分离出来了。然后,继续改进代码:
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, $value, $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 implements 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; }); // 然后在任何你想使用cache的地方 $cache = $di->get('cache');
我们新增加了一个接口BackendInterface,规定了redisDB,mongoDB,file这三个类必须实现这个接口所要求的功能,至于其他锦上添花的功能,随你怎么发挥。而cache的代码,好像没有变,因为cache不需要关心数据是怎么存入数据库或者文件中。而cache的调用者,也不需要关心cache具体是怎么实现的,只要根据接口实现相应的方法就行了。多人协作你会更加受益,你们只需要商定好接口,然后分别实现就行了。
这就是依赖注入的魅力所在了,虽然看似如此简单。
以上代码还可以继续改进,直到你认为无可挑剔为止。比如,redis服务在一个请求中可能会调用多次,而每次调用都会重新创建,这将有损性能。只需扩展一下DI容器就好增加一个参数或增加一个方法,随你。
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"); } ... }
这样以来,如果某个服务在一次请求中要调用多次,你就可以将shared属性设置为true,以减少不必要的浪费。如果你觉得每次在注入时都要setDi有点繁琐,想让他自动setDi,那可以这么做:
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); } // 如果实现了DiAwareInterface这个接口,自动注入 if (is_object($instance)) { if ($instance instanceof DiAwareInterface) { $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) { } }
然后,就可以这样:
$di->set('cache', function() { return new cache([ 'connect' => 'mongo' ]); });
我们现在所实现的这个DI容器还很简陋,还不支持复杂的注入,你可以继续完善它。
不过,通过这些代码你已经了解什么是依赖在注入了,你可以将这种思想应用到你的项目中,或者着手开发你自己的框架。
完。
-
php 依赖注入 数据库切换_php 依赖注入 和 控制反转 php设计模式
2021-01-12 22:04:19IOC:英文全称:Inversion of Control,中文名称:控制反转,它还有个名字叫依赖注入(Dependency Injection,简称DI)。当一个类的实例需要另一个类的实例协助时,在传统的程序设计过程中,通常由调用者来创建被调用者...什么是依赖注入?
IOC:英文全称:Inversion of Control,中文名称:控制反转,它还有个名字叫依赖注入(Dependency Injection,简称DI)。
当一个类的实例需要另一个类的实例协助时,在传统的程序设计过程中,通常由调用者来创建被调用者的实例。而采用依赖注入的方式,创建被调用者的工作不再由调用者来完成,因此叫控制反转,创建被调用者的实例的工作由IOC容器来完成,然后注入调用者,因此也称为依赖注入。
举个简单的例子:
(1)原始社会里,几乎没有社会分工。需要斧子的人(调用者)只能自己去磨一把斧子(被调用者)。
(2)进入工业社会,工厂出现。斧子不再由普通人完成,而在工厂里被生产出来,此时需要斧子的人(调用者)找到工厂,购买斧子,无须关心斧子的制造过程。
(3)进入“按需分配”社会,需要斧子的人不需要找到工厂,坐在家里发出一个简单指令:需要斧子。斧子就自然出现在他面前。
第一种情况下,实例的调用者创建被调用的实例,必然要求被调用的类出现在调用者的代码里。无法实现二者之间的松耦合。
第二种情况下,调用者无须关心被调用者具体实现过程,只需要找到符合某种标准(接口)的实例,即可使用。此时调用的代码面向接口编程,可以让调用者和被调用者解耦,这也是工厂模式大量使用的原因。但调用者需要自己定位工厂,调用者与特定工厂耦合在一起。
第三种情况下,调用者无须自己定位工厂,程序运行到需要被调用者时,依赖注入容器自动提供被调用者实例。事实上,调用者和被调用者都处于依赖注入容器的管理下,二者之间的依赖关系由依赖注入容器提供。因此调用者与被调用者的耦合度进一步降低,这使得应用更加容易维护,这就是依赖注入所要达到的目的。
用php实现一个轻量的依赖注入容器
首先我们创建一个类,看起来是这样的:
class Di
{
protected $_service = [];
public function set($name, $definition)
{
$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;
}
}
现在我们已经有了一个简单的类,包含一个属性和两个方法。假设我们现在有两个类,redisDB和cache,redisDB提供一个redis数据库的操作,cache负责缓存功能的实现并且依赖于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 }
}
在这个类中我们简单实现了redis的查询、保存和删除。你可能会有疑问,另外一个方法setDi是做什么的。待我继续为你讲解。另一个类和当前这个类结构很像:
class cache
{
protected $_di;
protected $_options;
protected $_connect;
public function __construct($options = null)
{
$this->_options = $options;
}
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);
}
}
现在我们就当已经实现了redisDB和cache这两个组件,具体的细节这里就先不做讨论了,来看看如何使用使用吧。首先需要将两个组件注入到容器中:
$di = new Di();
$di->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;
});
// 然后在任何你想使用cache的地方 $cache = $di->get('cache');
$cache->get('key'); // 获取缓存数据 $cache->save('key', 'value', 'lifetime'); // 保存数据 $cache->delete('key'); // 删除数据
到这里你可能会觉得这样以来反而有点繁琐了。cache和redisDB的结构如此之像,完全可以把redis写到cache中而没必要单独分离出来?
但是你想过没有,有些数据及时性没那么高而且数量比较大,用redis有点不合适,mongodb是更好的选择;有些数据更新频率更慢,对查询速度也没要求,直接写入文件保存到硬盘可能更为合适;再或者,你的客户觉得redis运维难度有点大,让你给他换成memcache… 这就是为什么把它分离出来了。然后,继续改进代码:
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, $value, $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 implements 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;
});
// 然后在任何你想使用cache的地方$cache = $di->get('cache');
我们新增加了一个接口BackendInterface,规定了redisDB,mongoDB,file这三个类必须实现这个接口所要求的功能,至于其他锦上添花的功能,随你怎么发挥。而cache的代码,好像没有变,因为cache不需要关心数据是怎么存入数据库或者文件中。而cache的调用者,也不需要关心cache具体是怎么实现的,只要根据接口实现相应的方法就行了。多人协作你会更加受益,你们只需要商定好接口,然后分别实现就行了。
这就是依赖注入的魅力所在了,虽然看似如此简单。
以上代码还可以继续改进,直到你认为无可挑剔为止。比如,redis服务在一个请求中可能会调用多次,而每次调用都会重新创建,这将有损性能。只需扩展一下DI容器就好增加一个参数或增加一个方法,随你。
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");
}
...
}
这样以来,如果某个服务在一次请求中要调用多次,你就可以将shared属性设置为true,以减少不必要的浪费。如果你觉得每次在注入时都要setDi有点繁琐,想让他自动setDi,那可以这么做:
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);
}
// 如果实现了DiAwareInterface这个接口,自动注入 if (is_object($instance)) {
if ($instance instanceof DiAwareInterface) {
$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) { }
}
然后,就可以这样:
$di->set('cache', function() {
return new cache([
'connect' => 'mongo'
]);
});
我们现在所实现的这个DI容器还很简陋,还不支持复杂的注入,你可以继续完善它。
不过,通过这些代码你已经了解什么是依赖在注入了,你可以将这种思想应用到你的项目中,或者着手开发你自己的框架。
以上内容希望帮助到大家,需要更多文章可以关注公众号:PHP从入门到精通,很多PHPer在进阶的时候总会遇到一些问题和瓶颈,业务代码写多了没有方向感,不知道该从那里入手去提升,对此我整理了一些PHP高级、架构视频资料和大厂PHP面试PDF免费获取,
-
理解php依赖注入和控制反转
2020-10-22 09:49:52主要帮助大家理解php依赖注入和控制反转,感兴趣的小伙伴们可以参考一下 -
依赖注入与控制反转_PHP控制反转(IOC)和依赖注入(DI)
2021-01-13 03:00:18IOC(inversion of control)控制反转模式;控制反转是将组件间的依赖关系从程序内部提到外部来管理; DI(dependency injection)依赖注入模式;依赖注入是指将组件的依赖通过外部以参数或其他形式注入; 两个说法...IOC(inversion of control)控制反转模式;控制反转是将组件间的依赖关系从程序内部提到外部来管理;
DI(dependency injection)依赖注入模式;依赖注入是指将组件的依赖通过外部以参数或其他形式注入;
两个说法本质上是一个意思。
例如:class DbMysql { public function query(){} } class Controller { public $db; public function __construct() { $this->db = new DbMysql(); } public function action() { $this->db->query(); } } $c = new Controller(); $c->action();
Controller类中的action方法需要用到DbMysql类中的query方法,所以Controller类就对DbMysql类产生了依赖,Controller类和DbMysql类之间的耦合度就比较高,因为当DbMysql类的构造函数发生改变的时候,比如由现在的没有参数变成有参数了,参数数量改变了,那么Controller类中的代码都要做出相应改变。
或者说我们现在需要将DbMysql类换成另一个DbOracle类,Controller类中要做出的改变甚至更大
看下面的另一种写法:
class DbMysql { public function query(){} } class Controller { public $db; public function __construct($dbMysql) { $this->db = $dbMysql; } public function action() { $this->db->query(); } } $db = new DbMysql(); $c = new Controller($db); $c->action();
Controller类中不需要实例化DbMysql,而是将DbMysql类的实例作为参数传递(或者单独写一个接收实例的方法处理),这样Controller类就完全不用管DbMysql是怎么样实例的,而是仅仅调用DbMysql中的query方法就行了。这种模式就是依赖注入。
第一个例子中Controller类负责实例DbMysql,也就是说Controller类控制着实例DbMysql类的主动权,而第二个例子中将这个主动权提出到Controller类的外面,所以也叫做控制反转。
这样看起来还不错,但是如果我们需要很多类,而且需要自己写的时候弄清楚,每一个类依赖什么类,这样太烦,如果有一个类能够帮我们搞定这个动作那就太爽了。而事实上有这个类,这个类就叫做IOC容器。
下面通过实例与大家分析分析
示例一
class DbMysql { public function __construct($host, $name, $pwd) { // do something } public function query() { echo __METHOD__ . PHP_EOL; } } class DbRedis { public function __construct($host, $name, $pwd) { // do something } public function set() { echo __METHOD__ . PHP_EOL; } } class controller { public $mysql; public $redis; public function __construct() { $this->mysql = new DbMysql('host', 'name', 'pwd'); $this->redis = new DbRedis('host', 'name', 'pwd'); } public function action() { $this->mysql->query(); $this->redis->set(); } } $c = new Controller(); $c->action(); /** * 输出: * DbMysql::query * DbRedis::set */
普通的实现方式,耦合度高。
示例二
class DbMysql { public function __construct($host, $name, $pwd) { // do something } public function query() { echo __METHOD__ . PHP_EOL; } } class DbRedis { public function __construct($host, $name, $pwd) { // do something } public function set() { echo __METHOD__ . PHP_EOL; } } class controller { public $mysql; public $redis; public function __construct($mysql, $redis) { $this->mysql = $mysql; $this->redis = $redis; } public function action() { $this->mysql->query(); $this->redis->set(); } } $mysql = new DbMysql('host', 'name', 'pwd'); $redis = new DbRedis('host', 'name', 'pwd'); $c = new Controller($mysql, $redis); $c->action(); /** * 输出: * DbMysql::query * DbRedis::set */
实现了依赖注入和控制反转,但是没有使用容器类。
示例三
class DbMysql { public function __construct($host, $name, $pwd) { // do something } public function query() { echo __METHOD__ . PHP_EOL; } } class DbRedis { public function __construct($host, $name, $pwd) { // do something } public function set() { echo __METHOD__ . PHP_EOL; } } class controller { public $mysql; public $redis; public function __construct($mysql, $redis) { $this->mysql = $mysql; $this->redis = $redis; } public function action() { $this->mysql->query(); $this->redis->set(); } } class Container { public $bindings = []; public function bind($key, Closure $value) { $this->bindings[$key] = $value; } public function make($key) { $new = $this->bindings[$key]; return $new(); } } $app = new Container(); $app->bind('mysql', function () { return new DbMysql('host', 'name', 'pwd'); }); $app->bind('redis', function () { return new DbRedis('host', 'name', 'pwd'); }); $app->bind('controller', function () use ($app) { return new Controller($app->make('mysql'), $app->make('redis')); }); $controller = $app->make('controller'); $controller->action(); /** * 输出: * DbMysql::query * DbRedis::set */
实现了基本的容器类,容器类中有两个方法,bind和make,一个是绑定操作,一个是实例化操作。将每一个需要使用到的类使用关键字绑定到容器类中去,但是每一个类仍然需要手动去实例化,这里引入了闭包函数,主要作用是在调用的时候才真正去实例化,而如果仅仅是绑定了一个类,是不会实例化这个类的。
示例四
class T { public $t; } class X { public $x; private function __construct() { } } class Y { public $x; public function __construct() { } } interface Is { } class Sis implements Is { } class S { public $s; public function __construct(string $s, int $i, array $a, Is $object) { $this->s = $s; } } function reflectionClass($className, array $inParams = []) { $reflection = new ReflectionClass($className); // isInstantiable() 方法判断类是否可以实例化 $isInstantiable = $reflection->isInstantiable(); if ($isInstantiable) { // getConstructor() 方法获取类的构造函数,为NULL没有构造函数 $constructor = $reflection->getConstructor(); if (is_null($constructor)) { // 没有构造函数直接实例化对象返回 return new $className; } else { // 有构造函数 $params = $constructor->getParameters(); if (empty($params)) { // 构造函数没有参数,直接实例化对象返回 return new $className; } else { // 构造函数有参数,将$inParams传入实例化对象返回 return $reflection->newInstanceArgs($inParams); } } } return null; } $t = reflectionClass('T'); var_dump($t instanceof T); $x = reflectionClass('X'); var_dump($x instanceof X); $x = reflectionClass('Y'); var_dump($x instanceof Y); $s = reflectionClass('S', ['asdf', 123, [1, 2], (new Sis)]); var_dump($s instanceof S); /** * 输出: * bool(true) * bool(false) * bool(true) * bool(true) */
引入反射类,他的作用是可以实例化一个类,和new操作一样。但是实例化一个类所需要的参数,他都能自动检测出来。并且能够检测出来这个参数是不是一个继承了接口的类。上面说一个类依赖另一个类,然后将另一个类作为参数注入,这个反射能够检测出一个类实例化的时候需要什么样的类,好像有点眉目了是吧。
示例五
class DbMysql { public function __construct($host, $name, $pwd) { // do something } public function query() { echo __METHOD__ . PHP_EOL; } } class DbRedis { public function __construct($host, $name, $pwd) { // do something } public function set() { echo __METHOD__ . PHP_EOL; } } class controller { public $mysql; public $redis; public function __construct($mysql, $redis) { var_dump($mysql);var_dump($redis); $this->mysql = $mysql; $this->redis = $redis; } public function action() { is_object($this->mysql) && $this->mysql->query(); is_object($this->redis) && $this->redis->set(); } } class Container { public $bindings = []; public function bind($key, $value) { if (!$value instanceof Closure) { $this->bindings[$key] = $this->getClosure($value); } else{ $this->bindings[$key] = $value; } } public function getClosure($value) { return function () use ($value) { return $this->build($value); }; } public function make($key) { if (isset($this->bindings[$key])) { return $this->build($this->bindings[$key]); } return $this->build($key); } public function build($value) { if ($value instanceof Closure) { return $value(); } // 实例化反射类 $reflection = new ReflectionClass($value); // isInstantiable() 方法判断类是否可以实例化 $isInstantiable = $reflection->isInstantiable(); if ($isInstantiable) { // getConstructor() 方法获取类的构造函数,为NULL没有构造函数 $constructor = $reflection->getConstructor(); if (is_null($constructor)) { // 没有构造函数直接实例化对象返回 return new $value; } else { // 有构造函数 $params = $constructor->getParameters(); if (empty($params)) { // 构造函数没有参数,直接实例化对象返回 return new $value; } else { $dependencies = []; // 构造函数有参数 foreach ($params as $param) { $dependency = $param->getClass(); if (is_null($dependency)) { // 构造函数参数不为class,返回NULL $dependencies[] = NULL; } else { // 类存在创建类实例 $dependencies[] = $this->make($param->getClass()->name); } } return $reflection->newInstanceArgs($dependencies); } } } return null; } } $app = new Container(); $app->bind('mysql', function () { return new DbMysql('host', 'name', 'pwd'); }); $app->bind('redis', function () { return new DbRedis('host', 'name', 'pwd'); }); $app->bind('controller', 'controller'); $controller = $app->make('controller'); $controller->action(); /** * 输出: * NULL * NULL */
容器类中引入反射,容器类bind方法升级,不仅仅支持闭包绑定,而且支持类名绑定。示例三中的bind方法仅仅支持绑定一个关键字为闭包,而类的实例操作,需要写到闭包函数中去。现在有了反射,可以直接使用类名,反射会根据类名自动去实例化这个类。
但是这个例子中输出两个NULL,Controller类的两个参数均为NULL,反射类并没有自动去找到Controller依赖的DbMysql和DbRedis去实例化。这是为什么呢?现在就需要引入另一个东西,针对接口编程。
这个例子中我们知道Controller类的两个参数是类,但是我们定义的构造函数中并没有声明,现在这种定义方式的两个参数,是类,是字符串,是整型,完全没区别的,反射类无法检测出这两个参数是类,我们的反射方法里面如果检测到构造函数的参数不是类直接返回NULL,所以这里输出了两个NULL。
示例六
interface SMysql { public function query(); } class DbMysql implements SMysql { public function __construct($host, $name, $pwd) { // do something } public function query() { echo __METHOD__ . PHP_EOL; } } interface SRedis { public function set(); } class DbRedis implements SRedis { public function __construct($host, $name, $pwd) { // do something } public function set() { echo __METHOD__ . PHP_EOL; } } class controller { public $mysql; public $redis; public function __construct(SMysql $mysql, SRedis $redis) { $this->mysql = $mysql; $this->redis = $redis; } public function action() { is_object($this->mysql) && $this->mysql->query(); is_object($this->redis) && $this->redis->set(); } } class Container { public $bindings = []; public function bind($key, $value) { if (!$value instanceof Closure) { $this->bindings[$key] = $this->getClosure($value); } else { $this->bindings[$key] = $value; } } public function getClosure($value) { return function () use ($value) { return $this->build($value); }; } public function make($key) { if (isset($this->bindings[$key])) { return $this->build($this->bindings[$key]); } return $this->build($key); } public function build($value) { if ($value instanceof Closure) { return $value(); } // 实例化反射类 $reflection = new ReflectionClass($value); // isInstantiable() 方法判断类是否可以实例化 $isInstantiable = $reflection->isInstantiable(); if ($isInstantiable) { // getConstructor() 方法获取类的构造函数,为NULL没有构造函数 $constructor = $reflection->getConstructor(); if (is_null($constructor)) { // 没有构造函数直接实例化对象返回 return new $value; } else { // 有构造函数 $params = $constructor->getParameters(); if (empty($params)) { // 构造函数没有参数,直接实例化对象返回 return new $value; } else { $dependencies = []; // 构造函数有参数 foreach ($params as $param) { $dependency = $param->getClass(); if (is_null($dependency)) { // 构造函数参数不为class,返回NULL $dependencies[] = NULL; } else { // 类存在创建类实例 $dependencies[] = $this->make($param->getClass()->name); } } return $reflection->newInstanceArgs($dependencies); } } } return null; } } $app = new Container(); $app->bind('SMysql', function () { return new DbMysql('host', 'name', 'pwd'); }); $app->bind('SRedis', function () { return new DbRedis('host', 'name', 'pwd'); }); $app->bind('controller', 'controller'); $controller = $app->make('controller'); $controller->action(); /** * 输出: * DbMysql::query * DbRedis::set */
使用接口了以后脚本执行正常。
首先容器绑定了接口SMysql和SRedis分别对应的闭包,绑定的关键字为接口名称,也就是说在容器中多次绑定一个接口只会绑定一个闭包一个类的实现,controller类的构造函数中声明了需要两个分别继承自SMysql接口和SRedis接口的类。容器中已经有了这两个接口的实现方式,所以直接调用闭包函数实例化类,然后将结果提供给controller类进行实例化。
本例中,我们仅仅make了controller类,也就是说我们只需要实例化controller类,而controller类依赖的DbMysql类和DbRedis类,IOC容器会自动帮我们实例化并注入。
本例中controller类的实例化很简单:
$app->bind('controller', 'controller'); $controller = $app->make('controller');
但是DbMysql和DbRedis就比较丑了:
$app->bind('SMysql', function () { return new DbMysql('host', 'name', 'pwd'); }); $app->bind('SRedis', function () { return new DbRedis('host', 'name', 'pwd'); });
其实是这两个类写的有问题,因为这两个类不是面对接口编程。但是这种自定义闭包函数的绑定非常方便,完全满足任何类的自由实例化。我们要做是依赖注入,那就全部使用接口来实现,看下一个例子。
示例七
interface MConfig { public function getConfig(); } class MysqlConfig implements MConfig { public function getConfig() { // 获取配置 return ['host', 'name', 'pwd']; } } interface RConfig { public function getConfig(); } class RedisConfig implements RConfig { public function getConfig() { // 获取配置 return ['host', 'name', 'pwd']; } } interface SMysql { public function query(); } class DbMysql implements SMysql { public $config; public function __construct(MConfig $config) { $this->config = $config->getConfig(); // do something } public function query() { echo __METHOD__ . PHP_EOL; } } interface SRedis { public function Set(); } class DbRedis implements SRedis { public function __construct(RConfig $config) { $this->config = $config->getConfig(); // do something } public function set() { echo __METHOD__ . PHP_EOL; } } class Controller { public $mysql; public $redis; public function __construct(SMysql $mysql, SRedis $redis) { $this->mysql = $mysql; $this->redis = $redis; } public function action() { is_object($this->mysql) && $this->mysql->query(); is_object($this->redis) && $this->redis->set(); } } class Container { public $bindings = []; public function bind($key, $value) { if (!$value instanceof Closure) { $this->bindings[$key] = $this->getClosure($value); } else { $this->bindings[$key] = $value; } } public function getClosure($value) { return function () use ($value) { return $this->build($value); }; } public function make($key) { if (isset($this->bindings[$key])) { return $this->build($this->bindings[$key]); } return $this->build($key); } public function build($value) { if ($value instanceof Closure) { return $value(); } // 实例化反射类 $reflection = new ReflectionClass($value); // isInstantiable() 方法判断类是否可以实例化 $isInstantiable = $reflection->isInstantiable(); if ($isInstantiable) { // getConstructor() 方法获取类的构造函数,为NULL没有构造函数 $constructor = $reflection->getConstructor(); if (is_null($constructor)) { // 没有构造函数直接实例化对象返回 return new $value; } else { // 有构造函数 $params = $constructor->getParameters(); if (empty($params)) { // 构造函数没有参数,直接实例化对象返回 return new $value; } else { $dependencies = []; // 构造函数有参数 foreach ($params as $param) { $dependency = $param->getClass(); if (is_null($dependency)) { // 构造函数参数不为class,返回NULL $dependencies[] = NULL; } else { // 类存在创建类实例 $dependencies[] = $this->make($param->getClass()->name); } } return $reflection->newInstanceArgs($dependencies); } } } return null; } } $app = new Container(); $app->bind('MConfig', 'MysqlConfig'); $app->bind('RConfig', 'RedisConfig'); $app->bind('SMysql', 'DbMysql'); $app->bind('SRedis', 'DbRedis'); $app->bind('controller', 'Controller'); $controller = $app->make('controller'); $controller->action(); /** * 输出: * DbMysql::query * DbRedis::set */
我们只需要实例化一个IOC容器,而后我们要使用到的所有类,它都能帮我们自动依赖注入。当然,这种实现是建立在组件针对接口编程上面的,否则就是示例六中的那种。
以上内容希望帮助到大家,很多PHPer在进阶的时候总会遇到一些问题和瓶颈,业务代码写多了没有方向感,不知道该从那里入手去提升,对此我整理了一些资料,包括但不限于:分布式架构、高可扩展、高性能、高并发、服务器性能调优、TP6,laravel,YII2,Redis,Swoole、Swoft、Kafka、Mysql优化、shell脚本、Docker、微服务、Nginx等多个知识点高级进阶干货需要的可以免费分享给大家,需要请戳这里
最后,祝所有大家在面试中过关斩将,拿到心仪offer。如果想与一群3-8年资深开发者一起交流学习的话,需要
请戳这里shimo.im -
依赖注入与控制反转_PHP的依赖注入(DI) 和 控制反转(IoC)
2021-01-13 03:00:05名词解释:IoC - Inversion of Control 控制反转 DI - Dependency Injection 依赖注入依赖注入和控制反转说的实际上是同一个东西,它们是一种设计模式,这种设计模式用来减少程序间的耦合依赖注入是从应用程序的角度...名词解释:
IoC - Inversion of Control 控制反转 DI - Dependency Injection 依赖注入
依赖注入和控制反转说的实际上是同一个东西,它们是一种设计模式,这种设计模式用来减少程序间的耦合
- 依赖注入是从应用程序的角度在描述,可以把依赖注入,即:应用程序依赖容器创建并注入它所需要的外部资源;
- 而控制反转是从容器的角度在描述,即:容器控制应用程序,由容器反向的向应用程序注入应用程序所需要的外部资源。
我们为什么使用依赖注入呢?
使用依赖注入
最重要的一点好处就是有效的分离了对象和它所需要的外部资源,使得它们松散耦合,有利于功能复用,
更重要的是使得程序的整个体系结构变得非常灵活
问题导读:
- 整个过程中参与者都有谁?
一般有三方参与者,一个是某个对象;一个是IoC/DI的容器;另一个是某个对象的外部资源。
某个对象指的就是任意的、普通的PHP对象;
IoC/DI的容器简单点说就是指用来实现IoC/DI功能的一个框架程序;
对象的外部资源指的就是对象需要的,但是是从对象外部获取的,都统称资源,<br> 比如:对象需要的其它对象、或者是对象需要的文件资源等等。
- 整个过程中参与者都有谁?
- 依赖:谁依赖于谁?为什么需要依赖?
- 注入:谁注入于谁?到底注入了什么?
- 控制反转:谁控制谁?控制什么?为何叫反转(有反转就应该有正转了,正转是什么呢?)
- 依赖注入和控制反转是同一概念吗?
1整个过程中参与者都有谁?
一般有三方参与者,一个是某个对象;一个是IoC/DI的容器;另一个是某个对象的外部资源。 某个对象指的就是任意的、普通的PHP对象; IoC/DI的容器简单点说就是指用来实现IoC/DI功能的一个框架程序; 对象的外部资源指的就是对象需要的,但是是从对象外部获取的,都统称资源,比如:对象需要的其它对象、或者是对象需要的文件资源等等。
2谁依赖于谁:
当然是某个对象依赖于IoC/DI的容器
3为什么需要依赖:
对象需要IoC/DI的容器来提供对象需要的外部资源
4谁注入于谁:
是IoC/DI的容器 注入 某个对象
5到底注入什么:
就是注入某个对象所需要的外部资源
6谁控制谁:
当然是IoC/DI的容器来控制对象了
76控制什么:
主要是控制对象实例的创建
8为何叫反转:
反转是相对于正向而言的,那么什么算是正向的呢? 考虑一下常规情况下的应用程序,如果要在A里面使用C,你会怎么做呢?当然是直接去创建C的对象,也就是说,是在A类中主动去获取所需要的外部资源C($c = new C();), 这种情况被称为正向的。那么什么是反向呢?就是A类不再主动去获取C,而是被动等待,等待IoC/DI的容器获取一个C的实例,然后反向的注入到A类中。
代码示意
class c { public function say() { echo 'hello'; } } /** * Class a */ class a { private $c; public function __construct() { $this->c = new C(); // 实例化创建C类 } public function sayC() { echo $this->c->say(); // 调用C类中的方法 } } $a = new a(); $a->sayC();
<?php /** * 当有了IoC/DI的容器后,a类依赖c实例注入的示例 */ /** * Class c */ class c { public function say() { echo 'hello'; } } /** * Class a */ class a { private $c; public function setC(C $c) { $this->c = $c; // 实例化创建C类 } public function sayC() { echo $this->c->say(); // 调用C类中的方法 } } $c = new C(); $a = new a(); $a->setC($c); $a->sayC();
9.什么是正转?正转就是按照普通的我们再类中直接创建对象实例,如 $c = new C();
10.依赖注入和控制反转是同一概念吗?
根据上面的讲述,我们不难出来,`依赖注入`和`控制反转`确实是对同一件事情的不同描述 从某个方面讲,就是它们描述的`角度`不同。
其实IoC/DI对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。<br>应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,<br>应用程序就变成被动的了,被动的等待IoC/DI容器来创建并注入它所需要的资源了。
以上内容希望帮助到大家,很多PHPer在进阶的时候总会遇到一些问题和瓶颈,业务代码写多了没有方向感,不知道该从那里入手去提升,对此我整理了一些资料,包括但不限于:分布式架构、高可扩展、高性能、高并发、服务器性能调优、TP6,laravel,YII2,Redis,Swoole、Swoft、Kafka、Mysql优化、shell脚本、Docker、微服务、Nginx等多个知识点高级进阶干货需要的可以免费分享给大家,需要
PHP进阶架构师>>>视频、面试文档免费获取docs.qq.com或者关注咱们下面的专栏
PHP架构师之路zhuanlan.zhihu.com来源:https://www.cnblogs.com/jackbon/p/12034454.html
-
依赖注入与控制反转_PHP的控制反转(IOC)和依赖注入(DI)概念
2021-01-05 08:56:04IOC(inversion of control)控制反转模式;控制反转是将组件间的依赖关系从程序内部提到外部来管理; DI(dependency injection)依赖注入模式;依赖注入是指将组件的依赖通过外部以参数或其他形式注入; 两个说法本质... -
控制反转
2019-09-19 23:50:32依赖注入介绍例子Laravel底层例子 介绍 一种设计模式,通过构造函数或一个方法,把一个外部实例注入进本类内部,而不是在本类内部创建外部实例,就是...src/Share.php中的Share类里面需要使用EasyWeChat\Foundatio... -
PHP控制反转(IOC)
2021-01-21 10:23:12控制翻转:IOC(inversion of control)控制反转模式;控制反转是将对3类(组件间)的依赖关系从程序内部提到外部来管理; 概念理解 我们先来介绍控制反转,依赖注入,这两个概念我们可以认为他们表达的同一种意思,举... -
PHP依赖注入、控制反转
2018-11-20 11:30:10要想理解 PHP 依赖注入 和 控制反转 两个概念,就必须搞清楚如下的两个问题: DI —— Dependency Injection 依赖注入 IoC —— Inversion of Control 控制反转 什么是依赖注入 没有你我就活不下去,那么,... -
依赖注入与控制反转_聊一聊PHP的依赖注入(DI) 和 控制反转(IoC)
2021-01-11 19:02:09前言最近在使用ThinkPHP5框架,看了下他的源码,发现有很多地方也用到了依赖注入(控制反转),觉得有必要和大家简单聊一聊什么是依赖注入以及怎么使用它。简介IoC - Inversion of Control 控制反转 DI - ... -
php依赖注入和控制反转
2019-12-30 21:34:08依赖注入是一种设计模式,又名 控制反转 ,为了降低耦合度 1:控制反转(Inversion of Control )的实现方式 简称:IOC 理解:A类不需要主动去获取C,而是被动等待,等待IoC/DI的容器获取一个C的实例,然后反向的... -
PHP实现控制反转
2017-02-25 00:31:45Fruit.php <?php /** *@authorGonn,http://www.nowamagic.net/ */ interfaceFruit{ publicfunctionshowColor(); } Apple.php classAppleimplem... -
ioc控制反转_【PHP】依赖注入(DI)和控制反转(IOC)详解
2020-11-12 16:14:13依赖注入和控制反转说的是同一个东西,是一种设计模式,这种设计模式用来减少程序间的耦合,先别追究这个设计模式的定义,否则你一定会被说的云里雾里,下面就以PHP的角度来描述一下依赖注入这个概念。先假设我们... -
php 依赖注入 和控制反转
2019-09-17 01:15:34Inversion of control 中文名称:控制反转/依赖注入 通常情况下:调用者来创建被调用者的实例,例如。 class a{} class b{ public function t(){ $c = new a(); ... -
谈谈php依赖注入和控制反转
2019-06-13 15:38:00要想理解php依赖注入和控制反转两个概念,就必须搞清楚如下的问题: DI——Dependency Injection 依赖注入 IoC——Inversion of Control 控制反转 1、参与者都有谁? 答:一般有三方参与者,一个是某个对象;... -
理解 PHP 依赖注入 和 控制反转
2019-03-19 16:42:00要想理解 PHP依赖注入和控制反转两个概念,就必须搞清楚如下的两个问题: DI—— Dependency Injection 依赖注入 IoC—— Inversion of Control 控制反转 什么是依赖注入 没有你我就活不下去,那么,你就是... -
PHP依赖倒置和控制反转
2018-10-08 18:11:00而在代码中体现出来的设计模式,就如依赖注入和控制反转。 那什么是依赖注入? 简单来说,就是把A类所依赖的B类C类等以属性或者构造函数等方式注入A类而不是直接在A类中实例化。 一般写代码我们这样写 class... -
php之依赖注入和控制反转
2019-06-04 21:21:58DI——Dependency Injection 依赖... IoC——Inversion of Control 控制反转 要想理解上面两个概念,就必须搞清楚如下的问题: 1、参与者都有谁? 答:一般有三方参与者,一个是某个对象;一个是IoC/DI...
-
读写锁
-
编程基础概念4
-
Liunx 优化思路与实操步骤
-
第二章 分支程序结构设计——作业-答案.html
-
Java执行Python脚本
-
小白学基金—初识基金
-
qengine:基于查询的处理引擎-源码
-
libFuzzer视频教程
-
【硬核】一线Python程序员实战经验分享(1)
-
多行元素的文本省略号
-
占据主动!刘强东微博营销之道.pdf
-
MySQL 多实例安装 及配置主从复制实验环境
-
jn82901336.github.io-源码
-
基于Qt的LibVLC开发教程
-
apache-jmeter-2.9.7z
-
中文NLP
-
Localyum配置
-
kesci-这十套练习,教你如何使用Pandas做数据分析-练习3(学习笔记)
-
物联网基础篇:快速玩转MQTT
-
华为1+X——网络系统建设与运维(中级)