初始写法
当A类使用B类,最开始的写法是:在A类内部新建B类的对象,然后使用。
例如,现在有个控制器类,需要从Repository类来获取数据。原始写法:
<?php
class Repository{
public function getData(){
return "data";
}
}
class Controller{
private $repository;
public function __construct(){
$this->repository = new Repository();
}
public function showData(){
$data = $this->repository->getData();
echo $data;
}
}
$c = new Controller();
$c->showData();
依赖注入
同样是A类使用B类:现在我们在外部创建B类的对象,然后传给A类。
现在A类不需要创建B类,只管调用B类的方法。
下面是从构造器注入的示例:
<?php
class Repository{
public function getData(){ return "data"; }
}
class Controller{
private $repository;
public function __construct(Repository $repository){
$this->repository = $repository;
}
public function showData(){
$data = $this->repository->getData();
echo $data;
}
}
//在其他的某个地方,实例化并且注入
$repository = new Repository();
$c = new Controller($repository);
$c->showData();
依赖反转
借鉴这篇文档:依赖反转准则
是指
依赖应该是接口/约定或者抽象类,而不是具体的实现。
示例:
<?php
//首先定义一个获取数据的接口
interface RepositoryInterface{
public function getData();
}
//一个具体的实现类,它从mysql数据库获取数据
class fromMysqlRepository implements RepositoryInterface{
public function getData(){
return 'this is data from mysql database';
}
}
class Controller{
private $repository;
//注入的依赖是一个接口
public function __construct(RepositoryInterface $repository){
$this->repository = $repository;
}
public function showData(){
$data = $this->repository->getData();
echo $data;
}
}
//实例化,注入
$repository = new fromMysqlRepository();
$c = new Controller($repository);
$c->showData();
某天,需要换一种获取数据的方式,比如从redis缓存中获取数据。这时候要修改的地方:
//新建一个类,也是实现RepositoryInterface接口,不过方法的具体实现是从redis获取数据。
class fromRedisRepository implements RepositoryInterface{
public function getData(){
return 'this is data from redis';
}
}
$repository = new fromRedisRepository();//实例化的地方,只需要修改这处代码
$c = new Controller($repository);
$c->showData();
一个初级的Ioc容器
上面的例子,都是需要手动创建依赖、注入。 创建依赖和注入,可以交给Ioc容器去做。
IOC控制反转:即
“创建对象实例的控制权从代码控制剥离到IOC容器控制。”
一个示例(来源于这里:https://www.insp.top/article/learn-laravel-container。):
interface RepositoryInterface{
public function getData();
}
class fromMysqlRepository implements RepositoryInterface{
public function getData(){
return 'this is data from mysql database';
}
}
class fromRedisRepository implements RepositoryInterface{
public function getData(){
return 'this is data from redis';
}
}
class Controller{
private $repository;
public function __construct(RepositoryInterface $repository){
$this->repository = $repository;
}
public function showData(){
$data = $this->repository->getData();
echo $data;echo '<br>';
}
}
//手动地创建依赖,并注入。现在要改变这个做法
//$repository = new fromMysqlRepository();
//$repository = new fromRedisRepository();
//$c = new Controller($repository);
//$c->showData();
//一个初级的容器
class Container
{
protected $binds;
protected $instances;
public function bind($abstract, $concrete){
if ($concrete instanceof Closure) {
$this->binds[$abstract] = $concrete;
} else {
$this->instances[$abstract] = $concrete;
}
}
public function make($abstract, $parameters = []){
if (isset($this->instances[$abstract])) {
return $this->instances[$abstract];
}
array_unshift($parameters, $this);
return call_user_func_array($this->binds[$abstract], $parameters);
}
}
// 创建容器
$container = new Container;
// 向容器添加Controller的创建脚本
$container->bind('controller', function($container, $moduleName) {
return new Controller($container->make($moduleName));
});
// 向容器添加fromMysqlRepository的创建脚本
$container->bind('mysql', function($container) {
return new fromMysqlRepository;
});
// 同上
$container->bind('redis', function($container) {
return new fromRedisRepository;
});
//在某处创建、调用。
$c1 = $container->make('controller',['mysql']);
$c1->showData();
解读
这个容器的例子来源于这里:https://www.insp.top/article/learn-laravel-container。
我读到这个例子时,看了好一会才看懂。 已经看懂的请忽略这个“解读”
当$container->make('controller',['mysql']);
的时候,发生了什么事?
1、第一次进入容器的make方法:
array_unshift($parameters, $this);
重新组装了$parameters,有2个元素:第一个是容器,第二个是字符串’mysql’。
call_user_func_array($this->binds[$abstract], $parameters);
此时 $this->binds[$abstract]
是刚才绑定的一个闭包controller
,也就是这样的内容:
function($container, $moduleName) {
return new Controller($container->make($moduleName));
}
2、
controller
这个闭包再次调用make
。第二次进入容器的make方法:
array_unshift($parameters, $this);
此时传进来的$parameters
是空数组,重新组装后,只有一个元素,也就是容器
call_user_func_array($this->binds[$abstract], $parameters);
此时 $this->binds[$abstract]
是另一个闭包 ‘mysql’,内容是
function($container) {
return new fromMysqlRepository;
};
3、所以,$container->make('controller',['mysql']);
这一句代码
做了2件事,先新建一个fromMysqlRepository对象;再新建一个controller对象,并且向它注入了上一步新建的fromMysqlRepository对象.
在Ioc容器中使用反射
laravel5.5 的容器位于vendor\laravel\framework\src\Illuminate\Container\Container.php
。
larave的Ioc容器中使用到了反射。 主要思路是通过反射获取对象的构造函数,进而获取构造函数参数,根据参数创建依赖,然后创建对象。
主要代码:
public function build($concrete){
if ($concrete instanceof Closure) { /* ...... */ }
$reflector = new ReflectionClass($concrete); // 获得反射的对象
if (!$reflector->isInstantiable()) { /* ...... */ }
$this->buildStack[] = $concrete;// 获取构造器
$constructor = $reflector->getConstructor();
if (is_null($constructor)) { /* ...... */ }
$dependencies = $constructor->getParameters(); // 获取构造器参数(一组ReflectionParameter 对象)
$instances = $this->resolveDependencies($dependencies); // 创建依赖
array_pop($this->buildStack);
return $reflector->newInstanceArgs($instances);// 利用依赖创建对象
}
protected function resolveDependencies(array $dependencies){
$results = [];
foreach ($dependencies as $dependency) {
if ($this->hasParameterOverride($dependency)) { /* ...依赖如果被重写过的处理... */ }
$results[] = is_null($dependency->getClass())
? $this->resolvePrimitive($dependency) //如果不是一个可实例化的类,就“bomb out with an error”
: $this->resolveClass($dependency); //没问题,就用make()创建依赖的实例
}
return $results;
}
protected function resolveClass(ReflectionParameter $parameter){
try {
return $this->make($parameter->getClass()->name);
} catch (BindingResolutionException $e) {
/* ...处理... */
throw $e;
}
}