控制反转(Inversion of Control,IoC)和依赖注入(Dependency Injection,DI)

  控制反转(Inversion of Control,简称IoC)是面向对象编程中的一种设计原则,目的是降低代码耦合度,而依赖注入(Dependency Injection,简称DI)则是控制反转的实现方式。
 
  假设开发者需要实现打电话方法,按照流程应该在打电话方法里面实例化一个手机对象,然后手机对象调用拨号方法完成打电话,实现代码如下:
 
<?php
declare(strict_types=1);
ini_set('display_errors', 'On');
error_reporting(-1);

/**
 * Human类
 */
class Human
{
    private string $name; // 姓名

    /**
     * 构造方法
     *
     * @param string $name 姓名
     */
    public function __construct(string $name)
    {
        $this->name = $name;
    }

    /**
     * 打电话
     *
     * @param string $num 通话目标号码
     * @return void
     */
    public function call(string $num): void
    {
        echo "{$this->name}即将打电话" . PHP_EOL;

        $mobile = new Mobile();
        $mobile->dial($num);
    }
}

/**
 * 手机类
 */
class Mobile
{
    /**
     * 拨号
     *
     * @param string $num 号码
     * @return void
     */
    public function dial(string $num): void
    {
        echo "手机拨号:$num" . PHP_EOL;
    }
}

$zhangsan = new Human('张三');
$zhangsan->call('18888888888');

//========== 输出过程·开始 ==========//
// 张三即将打电话
// 手机拨号:18888888888
//========== 输出过程·结束 ==========//
 
  上面的Human::call()会在方法体里创建Mobile类对象,然后调用Mobile::dial()实现打电话,即Human类依赖Mobile类,这就导致了一个问题:高耦合。
 
  我们可以把创建Mobile类对象这一操作移出Human::call(),然后在创建Human类对象时把Mobile对象作为参数传入,这就是最简单的依赖注入(从主动创建Mobile对象变成了被动接收Mobile对象,这就是所谓的反转),实现代码如下:
 
<?php
declare(strict_types=1);
ini_set('display_errors', 'On');
error_reporting(-1);

/**
 * Human类
 */
class Human
{
    private string $name;
    private Mobile $mobile;

    /**
     * 构造方法
     *
     * @param string $name 姓名
     * @param Mobile $mobile Mobile对象(依赖对象)
     */
    public function __construct(string $name, Mobile $mobile)
    {
        $this->name = $name;
        $this->mobile = $mobile;
    }

    /**
     * 打电话
     *
     * @param string $num 通话目标号码
     * @return void
     */
    public function call(string $num): void
    {
        echo "{$this->name}即将打电话" . PHP_EOL;
        $this->mobile->dial($num);
    }
}

/**
 * 手机类
 */
class Mobile
{
    /**
     * 拨号
     *
     * @param string $num 号码
     * @return void
     */
    public function dial(string $num): void
    {
        echo "手机拨号:$num" . PHP_EOL;
    }
}

$mobile = new Mobile();

// 通过构造器注入Mobile对象(依赖对象)
$zhangsan = new Human('张三', $mobile);
$zhangsan->call('18888888888');

//========== 输出过程·开始 ==========//
// 张三即将打电话
// 手机拨号:18888888888
//========== 输出过程·结束 ==========//
 
  通过构造器注入依赖对象有个很明显的缺点,就是如果有很多依赖对象,那么构造方法就要设置很多形参来接收这些依赖对象。比如Human类可能不止依赖Mobile类打电话,还会依赖Computer类发邮件、依赖Key类开门等等,如果这些依赖对象都通过构造器注入,那么Human类构造方法形参列表会很长。
 
  为了解决依赖对象多不好管理的问题,就出现了容器注入,简单来说就是把依赖对象全部放进容器(对象)里,这样构造器就可以只注入一个容器,需要使用什么对象就从容器里获取即可。可以把容器比作一个工具箱,需要什么工具就从工具箱里拿。

Copyright © 2024 码农人生. All Rights Reserved