转自:https://www.jksxit.com/essay/50
在软件工程领域,依赖注入(Dependency Injection)是用于实现控制反转(Inversion of Control)的最常见的方式之一。本文主要介绍依赖注入原理和常见的实现方式,重点在于介绍这种年轻的设计模式的适用场景及优势。
首先我们来一个实例,上代码
<?phpclass 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的方法里面也同样受影响,很明显可用性非常低
为了解耦,此时我们用到第二种思路:构造器注入,直接上代码:
<?phpclass 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