初识依赖注入和Ioc容器

来源:互联网 发布:淘宝分销是什么意思 编辑:程序博客网 时间:2024/05/23 19:25

初始写法

当A类使用B类,最开始的写法是:在A类内部新建B类的对象,然后使用。
例如,现在有个控制器类,需要从Repository类来获取数据。原始写法:

<?phpclass 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类的方法。
下面是从构造器注入的示例:

<?phpclass 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;    }}
原创粉丝点击