浅析依赖倒转、控制反转、IoC 容器、依赖注入。

用过 laravel 框架的人一定都听过控制反转依赖注入这个概念,对于很多初学者来说,对这两个概念很难理解,当初我也不例外,现在我就将我自己的理解分享出来,如果有什么不正确的地方还请指出。这里我不仅会给出这两个概念的见解,还会给出依赖倒置和 IOC容器 的见解。

一.依赖倒转。


开始之前,我们先来看一下什么是依赖倒转原则。依赖倒转原则书本上给出的说明是:

  • 高层模块不应该依赖于低层模块。它们都应该依赖抽象。
  • 抽象不应该依赖于细节,细节应该依赖于抽象。

在开始之前,我们先来理解几个概念,在这个过程中我会给一个老板,Hr,员工的例子。

  • 1,什么是依赖?

    老板需要做一件事情,然后这件事情能够完成的人是他的员工,这个时候老板就依赖员工。在编程中我理解的是:一个段代码的实现需要另外一段代码的参与,那么这就是一种依赖的关系。

  • 2.什么是高层模块,什么是低层模块?

    在这个例子中,老板就是高层,员工就是低层。在编程中我理解的是:实现低层操作的代码属于低层代码,比如说文件操作、数据库操作;通过引用底层代码来实现一些业务逻辑的代码就是高层代码。

为什么说高层模块不应该依赖低层模块呢?下面文字也许会让你明白。

老板的公司有一个新项目,需要招一个人去完成它,老板去招聘的时候不能直接随便找一个人就好了吧,他需要有一个招人的标准(能够完成项目能力的标准),这个时候老板依赖的是这个标准,只有应聘的人满足这个标准(应聘的人也依赖这个标准),老板才可能会招聘他。这个招聘的过程中,老板(高层模块)不依赖员工(低层模块),而是依赖能够完成项目的能力标准(高层模块依赖抽象),而员工也只有满足这个标准(低层模块依赖抽象),老板才可能招聘他。

上边的例子就说明了为什么高层模块不应该依赖底层模块,它们都应该依赖抽象。

  • 3.什么是抽象?

    抽象在oop中指的是:具有一类相同特征事物的集合。这个招聘过程中,这个招聘标准扮演抽象的角色,这个标准不需要完成老板的项目,只需要来约束应聘的员工具有这样的能力。

为什么说抽象不应该依赖于细节而细节应该依赖抽象?下面文字也许会让你明白。

老板为了员工能完成项目所规定的一个能力标准,这个标准他不需要依赖某个员工(标准提供在这里,如果员工技能满足就上,不满足就换人,所以抽象不依赖细节),员工的技能不满足这个标准,那么换另外一个人就好了。但是员工就不一样了,他的技能必须必须满足这个标准(这里的技能就是具体的实现细节,标准就是抽象,细节依赖抽象)后才有可能进入这家公司。

上边的例子说明了为什么抽象不应该依赖于细节,而细节应该依赖于抽象。

下面用代码来实现这个关系:

<?php
class Boos{

    //领导依赖员工
    private $staff;

    public function setStaff(){
            //来应聘的员工A
            $staff = new StaffA();
            //老板判断是否满足技能
            if($staff instanceof Standard){
                $this->staff = $staff;
            }else{
                throw new Exception('该员工没有我需要的工作能力');
            }
    } 

    public function task(){
        $this->staff->work();
    }
}

//招聘所设定的标准
interface Standard{
    public function work();
}

//员工需要依赖的标准
class StaffA implements Standard{
    public function work(){
        echo '雇员A有能力能够完成老板指定的工作';
    }
}

class StaffB implements Standard{
    public function work(){
        echo '雇员B有能力能够完成老板指定的工作';
    }
}

看了上面的例子,我想应该对依赖倒转有一定的了解了,我再总结一下:

上层调用模块和下层被调用模块都不应该彼此直接依赖对方,而是应该依赖一个抽象的规则(接口或者时抽象类),专业一点的说法就时编程要针对接口编程,不要针对实现来编程。这样写出的代码可维护,可拓展,灵活性好。

二.控制反转( IOC )、 IOC 容器和依赖注入( DI )。


控制反转(Inversion of Control,缩写为 IOC),书本上给出的解释是:

  • 模块间的依赖关系从程序内部提到外部来实例化管理称之为控制反转,这个实例化的过程就叫做依赖注入

在理解这个解释前我们先理解下下面几个词:

  • 程序内部。

    程序内部就是上层模块。

  • 程序外部。

    程序外部就是既不是上层模块,也不是下层模块的第三方模块或者是上下文。

我们继续看上边的老板招人的这段代码:

public function setStaff(){
            //来应聘的员工A
            $staff = new StaffA();
            //老板判断是否满足技能
            if($staff instanceof Standard){
                $this->staff = $staff;
            }else{
                throw new Exception('该员工没有我需要的工作能力');
            }
} 

发现什么没有,这个员工是老板亲自招的(员工类的实例化在老板类中),想想,如果老板的公司做大了,他还有这么多的时间去亲自招人吗?(如果老板类需要换一个员工类,每次都需要去改变老板类,违反了 开放-关闭 原则),老板亲自招人的这种做法就叫做控制正转(控制正传这个名字是我瞎想的,差不多就是这个意思)。

人都是聪明的,这个时候老板会去招一个 hr ,然后招人的时候老板只需要给 hr 说一下我现在需要招人, hr 就会把人找好然后交给老板。这个时候招人的控制权老板交给了 hr。这个就是控制反转,而这个 hr 可以把他当作一个 IOC 容器, hr 这个招人的动作可以理解为就是依赖注入。

我们来用代码实现控制反转的招人方式:

<?php
class Boos{

    //领导依赖员工
    private $staff;

    //现在老板只需要接受 hr 招聘就好,将控制权交给 hr
    //以设置方法来实现依赖注入
    public function setStaff(Standard $staff){      
        $this->staff = $staff;
    } 

    public function task(){
        $this->staff->work();
    }
}

//招聘所设定的标准
interface Standard{
    public function work();
}

//员工需要依赖的标准
class StaffA implements Standard{
    public function work(){
        echo '雇员A有能力能够完成老板指定的工作';
    }
}

class StaffB implements Standard{
    public function work(){
        echo '雇员B有能力能够完成老板指定的工作';
    }
}

//ioc容器
class Hr{
    public function getStagff(){
        return new StaffB();
    }
}

//公司老板
$boos = new Boos();
//老板招的hr
$hr = new Hr();
$staff = $hr->getStagff();
//hr把招到的人给老板(控制反转和依赖注入)
$boos->setStaff($staff);
//老板让他工作了
$boos->task();

上面的代码简单的实现了依赖反转,控制反转,ioc容器和依赖注入。

看了上边简单易懂的代码后,我将上边的代码重写一下,这次重写仿照 laravel 的IOC容器和依赖注入:

<?php
class Boos{

    //领导依赖员工
    private $staff;

    //老板只需要告诉外部我需要什么样的人就好了,其它什么都不管,具体什么样的人交给外部处理。
    //用构造方法方式实现依赖注入
    public function __construct(Standard $staff){       
        $this->staff = $staff;
    } 

    public function task(){
        $this->staff->work();
    }
}

//招聘所设定的标准
interface Standard{
    public function work();
}

//员工需要依赖的标准
class StaffA implements Standard{
    public function work(){
        echo '雇员A有能力能够完成老板指定的工作';
    }
}

class StaffB implements Standard{
    public function work(){
        echo '雇员B有能力能够完成老板指定的工作';
    }
}

class Hr{

    private $binds = [];

    //接受不同员工的简历,并存起来
    public function bind($contract,$concrete){
        $this->binds[$contract] = $concrete;
    }

    //询问老板选人的标准由哪些,并且从满足的简历中筛选人
    private function methodBindParams($className){
        $reflect = new reflect($className,'__construct');
        return $reflect->bindParamsToMethod();
    }

    //将选好的工作人员交给老板
    public function make($className){
        $methodBindParams = $this->methodBindParams($className);
        $reflect = new reflect($className,'__construct');
        return $reflect->make($this->binds,$methodBindParams);
    }
}

class reflect{
    private $className;

    private $methodName;

    public function __construct($className,$methodName){
        $this->className = $className;
        $this->methodName = $methodName;
    }

    //绑定参数到方法
    public function bindParamsToMethod(){

        $params = [];

        $method  = new ReflectionMethod($this->className,$this->methodName);

        foreach ($method->getParameters() as $param) {
            $params[] =  [$param->name,$param->getClass()->name];
        }

        return [$this->className=> $params];
    }

    public function make($bind,$methodBindParams){
        $args = [];
        foreach ($methodBindParams as $className => $params) {
            foreach ($params as $param) {
                list($paramName,$paramType) = $param;

                $paramName = new $bind[$paramType]();

                array_push($args, $paramName);
            }
        }
        $reflectionClass = new ReflectionClass($this->className);
        return $reflectionClass->newInstanceArgs($args);
    }

}
$hr = new Hr();

//老板如果需要换工作人员,只需要绑定其它的工作人员即可。
$staff = $hr->bind('Standard','StaffA');

$boos = $hr->make('Boos');

$boos->task();

以上就是我的理解,如果由什么不正确的地方,还请大家多多指出。

本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由系统于 5年前 自动加精
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 6

简单点理解其实就是 多态+形参

5年前 评论

写的真好,一直迷糊这些,今天看了,豁然开朗!

5年前 评论

这个例子很形象,666

5年前 评论

看了这个列子,终于有点理解什么是依赖倒转,控制反转,ioc容器,依赖注入了。

5年前 评论

666,看到的IOC和DI讲的最通俗易懂的文章

5年前 评论

牛逼!我愿称你为史上最强解析IOC和DI的使者!

3年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!