在软件工程领域,依赖注入(Dependency Injection)是用于实现控制反转(Inversion of Control)的最常见的方式之一。本文主要介绍依赖注入原理和常见的实现方式,重点在于介绍这种年轻的设计模式的适用场景及优势。
首先我们来一个实例,上代码
<?php
class A
{
public function test()
{
echo 'this is A!<br>';
$b = new B();
$b->test();
}
}
class B
{
public function test()
{
echo 'this is B!<br>';
$c = new C();
$c->test();
}
}
class C
{
public function test()
{
echo 'this is C!<br>';
}
}
$obj = new A();
$obj->test();
结果是:
this is A!
this is B!
this is C!
从代码分析,A类依赖B类,B类依赖C类。这是我们最原始的实现思路.这种实现思路很明显会有问题
假如我们现在B类修改下,代码如下:
class B
{
public $name;
public function __construct($name)
{
$this->name = $name;
}
public function test()
{
echo 'this is B'.$this->name.'!<br>';
$c = new C();
$c->test();
}
}
此时再看我们原来A类test方法直接调用明显会有问题,于是此时我们需要将原来代码:
class A
{
public function test()
{
echo 'this is A!<br>';
$b = new B();
$b->test();
}
}
修改成:
class A
{
public function test()
{
echo 'this is A!<br>';
$b = new B('(class B)');//构造的时候多加了一个参数
$b->test();
}
}
如果此时C类构造方法也变动了呢,B的方法里面也同样受影响,很明显可用性非常低
为了解耦,此时我们用到第二种思路:构造器注入,直接上代码:
<?php
class A
{
public $obj;
public function __construct(B $b)
{
$this->obj = $b;
}
public function test()
{
echo 'this is A!<br>';
$this->obj->test();
}
}
class B
{
public $obj;
public function __construct(C $c)
{
$this->obj = $c;
}
public function test()
{
echo 'this is B!<br>';
$this->obj->test();
}
}
class C
{
public function test()
{
echo 'this is C!<br>';
}
}
$c = new C();
$b = new B($c);
$obj = new A($b);
$obj->test();
这种方法可以解决第一种方法,如果依赖的类构造方法(比如B类)有所改动,A类不需要改动任何代码。但是久而久之,这种方法毛病也出来了,我们首先必须要先实例化C类,再实例化B类,最后再实例化A类。
了解到第二种方案需要优化,于是出现了基本成型的第三种方案,此时我们用到了container(容器)和instance(实例),直接上代码:
class Container
{
public static $_definations = [];
public function get($class)
{
//当前类所依赖的类
$depends = [];
$tc = new ReflectionClass($class);
//得到构造方法
$constructor = $tc->getConstructor();
//得到构造方法的参数
if($constructor !== NULL)
{
foreach($constructor->getParameters() as $parameter)
{
if($parameter->isDefaultValueAvailable())
{
$depends[] = $parameter->getDefaultValue();
}
else
{
$pc = $parameter->getClass();
$instance = Instance::getInstance($pc == NULL ? NULL : $pc->getName());
$depends[] = $instance;
}
}
}
foreach($depends as $k => $v)
{
if($v instanceof Instance)
{
if($v->id !== NULL)
{
$depends[$k] = $this->get($v->id);
}
}
}
$tm_instance = $tc->newInstanceArgs($depends);
return $tm_instance;
}
}
class Instance{
/**
* @var 类唯一标示
*/
public $id;
/**
* 构造函数
* @param string $id 类唯一ID
* @return void
*/
public function __construct($id)
{
$this->id = $id;
}
/**
* 获取类的实例
* @param string $id 类唯一ID
* @return Object Instance
*/
public static function getInstance($id)
{
return new self($id);
}
}
class Base{
}
class A extends Base{
private $instanceB;
public function __construct(B $instanceB)
{
$this->instanceB = $instanceB;
}
public function test()
{
echo 'this is A!<br/>';
$this->instanceB->test();
}
}
class B extends Base{
private $instanceC;
public function __construct(C $instanceC)
{
$this->instanceC = $instanceC;
}
public function test()
{
echo 'this is B!<br/>';
return $this->instanceC->test();
}
}
class C extends Base{
public function test()
{
echo 'this is C!';
}
}
$container = new Container();
$obj_a = $container->get('A');
$obj_a->test();
此方法有参考yii2中yii2\di\container实现,只是将它简化了,更容易看懂。重点看看container的get方法
现在我们增加set方法,自定义函数,直接上代码精简版
class Container
{
/**
*@var array 存储各个类的定义 以类的名称为键
*/
public static $_definations = [];
public function get($class, $params = [], $props = [])
{
if(!isset(self::$_definations[$class]))
return $this->build($class, $params, $props);
//如果已经被定义过的
$_defination = self::$_definations[$class];
if(is_callable($_defination, true))
{
return call_user_func($_defination, $this, $params, $props);
}
else if(is_object($_defination))
{
return $_defination;
}
else
{
throw new Exception($class . '声明错误');
}
}
public function set($class, $_defination)
{
self::$_definations[$class] = $_defination;
}
public function build($class, $params, $props)
{
//当前类所依赖的类
$depends = [];
$tc = new ReflectionClass($class);
//得到构造方法
$constructor = $tc->getConstructor();
//得到构造方法的参数
if($constructor !== NULL)
{
foreach($constructor->getParameters() as $parameter)
{
if($parameter->isDefaultValueAvailable())
{
$depends[] = $parameter->getDefaultValue();
}
else
{
$pc = $parameter->getClass();
$instance = Instance::getInstance($pc == NULL ? NULL : $pc->getName());
$depends[] = $instance;
}
}
}
foreach($depends as $k => $v)
{
if($v instanceof Instance)
{
if($v->id !== NULL)
{
$depends[$k] = $this->get($v->id);
}
}
}
$tm_instance = $tc->newInstanceArgs($depends);
return $tm_instance;
}
}
增加了一个set方法,并将get方法稍微改版了一下,现在我们看看怎么调用set方法:
$container = new Container();
$container->set('foo', function($container, $params, $config){
print_r($params);
print_r($config);
});
$container->get('foo', ['p1' => 'pv1'], ['c1' => 'cv1']);
输出结果为:
Array ( [p1] => pv1 ) Array ( [c1] => cv1 )
再看看另外一种情况调用:
class Test
{
public function mytest()
{
echo 'this is a test';
}
}
$container->set('testObj', new Test());
$test = $container->get('testObj');
$test->mytest();
输出结果为:
this is a test
上面的代码只是作者粗略的写了下简单版本,很多地方不是太完善,这版本是为了理解yii2的container打下基础,稍后会出yii2的依赖注入源码分析
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。