依赖注入,控制反转,反射各个概念的理解和使用

未匹配的标注

控制反转,依赖注入概念理解

我们先来介绍控制反转,依赖注入,这两个概念我们可以认为他们表达的同一种意思,举个通俗的例子,我们用户登录需要提供记录日志的功能,可以选择使用文件或者数据库。下面我们用代码来演示。

// 定义写日志的接口规范
interface Log
{
    public function write();   
}

// 文件记录日志
class FileLog implements Log
{
    public function write(){
        echo 'file log write...';
    }   
}


// 数据库记录日志
class DatabaseLog implements Log
{
    public function write(){
        echo 'database log write...';
    }   
}

// 程序操作类
class User 
{
    protected $fileLog;

    public function __construct()
    {
        $this->fileLog = new FileLog();   
    }

    public function login()
    {
        // 登录成功,记录登录日志
        echo 'login success...';
        $this->fileLog->write();
    }

}

$user = new User();
$user->login();

上面的写法可以实现记录日志的功能,但是有一个问题,假设现在想用数据库记录日志的话,我们就得修改 User 类,这份代码没达到解耦合,也不符合编程开放封闭原则,那如何修改呢?我们可以把日志处理类通过构造函数方式传递进去。下面我们试着修改 User 类的代码。

class User 
{
    protected $log;

    public function __construct(Log $log)
    {
        $this->log = $log;   
    }

    public function login()
    {
        // 登录成功,记录登录日志
        echo 'login success...';
        $this->log->write();
    }

}

$user = new User(new DatabaseLog());
$user->login();

这样想用任何方式记录操作日志都不需要去修改 User 类了,只需要通过构造函数参数传递就可以实现,其实这就是“控制反转”。不需要自己内容修改,改成由外部传递。这种由外部负责其依赖需求的行为,我们可以称其为 “控制反转(IoC)”。

那什么是依赖注入呢?,其实上面的例子也算是依赖注入,不是由自己内部 new 对象或者实例,通过构造函数,或者方法传入的都属于 依赖注入(DI) 。

laravel依赖注入

初学laravel的同学应该都比较好奇?很多对象实例通过方法参数定义就能传递进来,调用的时候不需要我们自己去手动传入。下面举一个laravel中实际的例子 Request 对象 会都被自动的注入到函数里。是不是比较好奇呢?laravel 是如何做到呢?

// routes/web.php
Route::get('/post/store', 'PostController@store');

// App\Http\Controllers
class PostController extends Controller {

    public function store(Illuminate\Http\Request $request)
    {
        $this->validate($request, [
            'category_id' => 'required',
            'title' => 'required|max:255|min:4',
            'body' => 'required|min:6',
        ]);
    }

}

反射理解

我们现在已经明白了依赖注入的概念。那 laravel 是怎么实现呢?可能有些同学已经想到了这里面肯定会用到反射机制去创建动态Post,然后去调用 store 方法。

反射的概念其实可以理解成根据类名返回该类的任何信息,比如该类有什么方法,参数,变量等等。我们先来学习下反射要用到的 api。拿 User 举例


// 获取User的reflectionClass对象
$reflector = new ReflectionClass(User::class);

// 拿到User的构造函数
$constructor = $reflector->getConstructor();

// 拿到User的构造函数的所有依赖参数
$dependencies = $constructor->getParameters();


// 创建user对象
$user = $reflector->newInstance();

// 创建user对象,需要传递参数的
$user = $reflector->newInstanceArgs($dependencies = []);

这时候我们可以创建一个 make 方法,传入 User,利用反射机制拿到 User 的构造函数,进而得到构造函数的参数对象。用递归的方式创建参数依赖。最后调用 newInstanceArgs 方法生成 User 实例。 可能有些同学还不是很理解。下面我们用代码去简单模拟下

function make($concrete){
    // 或者User的反射类
    $reflector = new ReflectionClass($concrete);
    // User构造函数
    $constructor = $reflector->getConstructor();
    // User构造函数参数
    $dependencies = $constructor->getParameters();
    // 最后生成User    
    return $reflector->newInstanceArgs($dependencies);
}

$user = make('User');
$user->login();

具体代码实现

// 注意我们这里需要修改一下User的构造函数,如果不去修改。反射是不能动态创建接口的,那如果非要用接口该怎么处理呢?下一节我们讲Ioc容器的时候会去解决。

class User 
{
    protected $log;

    public function __construct(FileLog $log)
    {
        $this->log = $log;   
    }

    public function login()
    {
        // 登录成功,记录登录日志
        echo 'login success...';
        $this->log->write();
    }

}

function make($concrete){

    $reflector = new ReflectionClass($concrete);
    $constructor = $reflector->getConstructor();
    // 为什么这样写的? 主要是递归。比如创建FileLog不需要传入参数。
    if(is_null($constructor)) {
        return $reflector->newInstance();
    }else {
        // 构造函数依赖的参数
        $dependencies = $constructor->getParameters();
        // 根据参数返回实例,如FileLog
        $instances = getDependencies($dependencies);
        return $reflector->newInstanceArgs($instances);
    }

}

function getDependencies($paramters) {
    $dependencies = [];
    foreach ($paramters as $paramter) {
        $dependencies[] = make($paramter->getClass()->name);
    }
    return $dependencies;
}

$user = make('User');
$user->login();

到这里,我们依赖注入,控制反转,反射也就讲完了。

示例代码下载链接

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
cxp1539
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
贡献者:8
讨论数量: 4
发起讨论 只看当前版本


user02
$request->validate () 和 $this->validate () 有什么区别?
5 个点赞 | 3 个回复 | 问答 | 课程版本 5.5
lalall
这种写法的劣势在哪,是不是个人习惯
0 个点赞 | 17 个回复 | 问答 | 课程版本 5.5
merge
以 Request 为例,以下三种写法有什么不同么?
0 个点赞 | 6 个回复 | 问答 | 课程版本 5.5
heibai_2018
为啥定义接口名是小写,引入的时候又是大写
0 个点赞 | 2 个回复 | 分享 | 课程版本 5.5