thinkphp6的中阶教程

依赖注入 和 控制反转

其实 依赖注入 和 控制反转 说的是同一件事情,只是站的角度不同而已。
我们就拿超人和小怪兽的事情来做类比对象。

地球受到了威胁,不断有小怪兽来想要破坏地球,每来一个小怪兽我们就需要找一个超人去对付他,一个超人肯定是不够的,因为每次来到小怪兽都是不一样的,他们所具有的能力也是不一样的。

因此我们必须找到合适的超人去对付他,最坏的情况是每来一个小怪兽我们就要找一个或者制造一个新超人,那么来十个小怪兽,我们就要制造十个,来百个就要制造百个,来千,来万,来亿我们就要制造相应的超人,而大部分超人只能用一次。

为了解决这个问题我们引入依赖注入和控制反转的概念,我们将超人和超能力分开,独立的超人和独立的超能力,当一个小怪兽来的时候我们找到超人,将相应的超能力给予他,让他去消灭小怪兽。

这样的话我们只需要几个超人就好了,我们不再需要制造超人,而是研究如何制造更多更好的超能力给超人使用。

超能力和超人不再是强依赖关系。超能力是由外部给予超人的,超人和超能力有依赖,但是这个依赖是外部给予,因此我们可以说超能力是由外部注入给他的,所以这就叫 依赖注入。

而反过来说,超人具有何种超能力不是他内部自身控制的,而是由外部控制的,相当于将超能力具有何种功效交给了外部,外部来决定超人该有的超能力,所以超能力的控制权被由自身控制反转为外部控制,这被称为 控制反转。

这就是关于 依赖注入 和 控制反转 的我的比较好理解的解释。它能较好的解决对象与对象之间的强耦合问题,同时也能做的按需使用按需加载。


实例:Class A 中用到了 Class B 的对象 b,一般情况下,需要在 A 的代码中显式的 new 一个 B 的对象。
采用依赖注入技术之后,A 的代码只需要定义一个私有的 B 对象,不需要直接 new 来获得这个对象,而是通过相关的容器控制程序来将 B 对象在外部 new 出来并注入到 A 类里的引用中。

这样做有什么好处呢?
解释:假如现在有 N 多个类,需要用到 Class B, 那就需要在类里面实例化 N 多次,这样对于后期的维护和管理都是不方便的,如果后期需求发生改变,那更改量有大很多。

依赖注入的具体实现:

image.png

上面代码中的就是依赖注入,现在又有一个问题
假如,现在类 b 中的 b 方法现在改名为 c 了,那 a 类里面的 a 方法中,就要将 b 类中调用的 b 方法改成 c 方法。那依旧涉及到那个问题,如果有 N 多个类要用到 B 类呢?那需要做的也是去 N 多个类中不断得去改,这样对后期得维护,需求更改都是要花费很大的成本。

直接贴代码:


image.png

图中可以看到,b 类是一个接口类,c,d 继承了 b 类,接口类的具体规则。
如果将C类中是b()改为c(),那编辑器会提示错误,由于接口必须有b函数,所以再写一个b函数即可。


TP6中 自动进行依赖注入

namespace app\controller;
class Foo 
{
    public function __construct(Bar $bar)
    {
    }
}

如果直接new的话,需要手动传入Bar对象实例

$bar = new Bar(); 
$foo = new Foo($bar);

如果使用invoke的话,可以自动进行依赖注入。

$foo = invoke('app\controller\Foo'); //不需要先new Bar,是自动进行依赖注入

容器

一个基本的服务容器
先搞清楚服务的概念,不要想多了,任何一个功能,任务都可以叫做服务 service。所以说功能类对象,就是服务。
再看容器,把这些服务装在一起,装在哪?就是一个容器。其实就是一个可以找到这些服务的一个对象。

通常一个容器要具有绑定和解析两个操作。

绑定,指的是将获取服务对象的方法在容器中进行注册。相当于将服务装入到了容器中。
解析,指的是将绑定到容器中的服务从容器中提取出来,注意通常我们绑定的不是对象本身,而是生成对象的代码,因此解析时通常是执行代码来得到对象。

# 1, 服务容器定义
/**
 * Class Application
 * 服务容器类,类名参考Laravel
 */
class Application
{
//    已绑定(注册)的服务
    private $services = [];

    /**
     * 绑定(注册)
     * @param $class string
     * @param $generator Closure
     */
    public function bind($class, $generator)
    {
        $this->services[$class] = $generator;
    }

    /**
     * 解析
     * @param $class string
     */
    public function make($class)
    {
        //return $this->services[$class]; //这样返回的是函数,不是函数结果
        return call_user_func($this->services[$class]); //这样是运行函数,返回函数结果
    }
}

# 2, 服务类示例
/**
 * Class Kernel
 * 内核服务类
 */
class Kernel
{
}
/**
 * Class Request
 * 请求服务类
 */
class Request
{
}

# 3, 绑定服务到容器,通常在程序初始化阶段完成
$app = new Application();
$app->bind('Kernel', function() {
    return new Kernel();
});
$app->bind('Request', function() {
    return new Request();
});

# 4, 需要时从容器中解析
$kernel = $app->make('Kernel');
var_dump($kernel);
$request = $app->make('Request');
var_dump($request);

观察上面的代码,Application 类就是服务容器类,实例化的 $app 就是服务容器对象。服务都绑定在这个容器上。
Kernel 和 Request 就是具体的某个服务。别忘了任何功能都可以是服务。
$app->bind (),就是将服务绑定到容器中,注意,绑定的是对象生成代码,而不是对象本身。因此绑定时,并没有去实例化对象。
$app->make (),就是从服务容器中解析服务,其实就是调用对应类的生成代码,得到对应的服务对象。

以上就是一个基本的服务容器。提供服务容器这种架构的目的,就是将项目中各种各样复杂多样,需要复用的功能整理到一起来管理。

什么情况适合放入容器

  • 容器中的类并不会自动执行,而是调用时执行
  • tp6容器中已经调用过的类会自动使用单例,所以这是容器的优势

适合放入容器,不放服务中:
工具类(方便、并且是单例)


TP6的服务

就是需要在控制器之前,先完成一些配置、操作等,可放入服务中。
因为服务的执行优先级高
1、会先执行 自定义服务类的 register() 方法 将服务绑定到容器中
2、再执行 自定义服务类的 boot() 启动方法,执行函数内的操作

适合放入服务:
微信、短信、上传、用户、管理员、配置信息等,放入容器


中间件

中间件主要用于拦截或过滤应用的HTTP请求,并进行必要的业务处理。

适合中间件:
用户、管理员、配置信息等从容器中调用对应的方法,自动单例

跟http请求相关的放 中间件,不相关的放 服务


请求顺序

image.png

你可能感兴趣的:(thinkphp6的中阶教程)