深入浅出之DI、IOC

960 阅读2分钟

前言

OOP(面向对象编程),早已深入人心,我们的程式也因此更具扩展性。但是,诸多的对象互相依赖,也让程式复杂性加深,DI(依赖注入)、IOC(容器化)的出现,便是解决此问题。接下来,我们通过实例,让大家更好的理解掌握。

DI 依赖注入

牧场饲养牛群,我们用程式表示

class Farm {
    public $animal;

    public function __construct() {
        $this->animal = new Cattle();
    }
}

interface Animal {
    public function sound();
}

class Cattle implement Animal{
    public function __construct() {}
    public function sound() {
        echo "Cattle makes a sound:mang~"
    }
}

class Sheep implement Animal{
    public function __construct() {}
    public function sound() {
        echo "Sheep makes a sound:mie~"
    }
}

可见,Farm类与Cattle类产生依赖关系,并且,在Farm类内部实例化了Cattle类。显然违反了我们的"开闭原则"。

我们用依赖注入的方式,改写程式

class Farm {
    public $animal;

    public function __construct(Animal animal) {
        $this->animal = new animal();
    }
}

$Farm = new Farm(new Cattle());

Cattle类就和Farm类完成解耦,后续的依赖以注入的形式完成。

依赖注入(Dependency Injection):在程式运行过程中,动态将某种依赖关系注入到对象中

也可以简单理解为:以参数的形式,传递所需对象,到另外一个对象中。

Laravel中的依赖注入

class ExampleController {
   public function Index(Request $request) {
       var_dump($request->all());
   }
}

Laravel框架内的Request传参,有应用到反射来实现DI

// 构造反射函数
$method = new ReflectionMethod('ExampleController', 'Index');
$args = [];
foreach($method->getParameters() as $parameter) {
    if ($class = $parameter->getClass()) {
        $args[] = new $class->name; //$request = new Request
    }
}
$method->invokeArgs(new ExampleController, $args);
  • ReflectionMethod,反射函数;ReflectionClass,反射类
  • getParameter,循环参数组,如果是类,便实例化
  • invokeArgs,使用数组给方法传递参数,并执行它

IOC 容器化

IOC (Inversion of Control):借助“第三方”,实现具有依赖关系的对象之间解耦

依赖关系即Farm类和Cattle类,那么第三方容器是什么,又如何通过它实现依赖关系的解耦呢,我们通过代码@来源来释义:

class Container
{
    protected $binds; //注册实例

    protected $instances; //实例

    /**
     * 向容器注册实例
     */
    public function bind($abstract, $concrete)
    {
        if ($concrete instanceof Closure) {
            // 绑定闭包,懒加载实例,此时未new实例
            $this->binds[$abstract] = $concrete;
        } else {
            // 绑定实例
            $this->instances[$abstract] = $concrete;
        }
    }

    /**
     * 从容器拿并new实例
     */
    public function make($abstract, $parameters = [])
    {
        // 如果类已new,直接返回,无需懒加载
        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; // 生成容器

$container->bind('Animal', function($container){
    return new Cattle;
}); // 向容器注册Animal

$container->bind('Farm', function($container,$module){
    return new Farm($container->make($module));
}); // 向容器注册Farm

$computer = $container->make('Farm','Animal'); // 实例化Farm,Animal作为参数,在Farm闭包内完成实例,进而完成依赖注入
  • 通过懒加载节省性能开销,Farm类、Cattle类开始注册到容器类,只有正式使用,才会new实例
  • 重设Farm类和Animal类关联,我们只需要改动Animal绑定下的Cattle即可

参考资料

Laravel中的服务容器 涂bug

浅谈IOC:说清楚IOC是什么 DebugLZQ

Laravel中应用反射实现依赖注入 PHP进阶架构师 ​