陈大剩博客

php设计模式终篇:一文读懂:依赖注入、控制反转、IoC容器

  • 陈大剩
  • 2023-06-16 00:07:48
  • 2185

前提

目前设计模式的文章全已经连载完成,下阶段将开启 Laravel 源码解读系列。

网上对:依赖注入、控制反转、IoC容器 的描述众说纷纭,模模糊糊的,便自整理一遍,以巩固一下知识。之前工作忙于开发,粗制看了一遍,只懂其原理,没有细致深入,最近阅读 Laravel 源码,才再续前缘。

依赖注入

依赖注入是一种设计方法,下面将从两方面来展开谈谈。

什么是依赖?

某类中需要另一个类完成某类中工作,如:A 类需要 B 类完成特定的工作。

// A 类
class A
{
    public function __construct()
    {
        $b = new B();
        // do something
    }
}

// B 类
class B
{
    public function __construct()
    {
        return '我是 B 类';
    }
}

本例举例是在构造函数中依赖,除构造方法外其他方法也是一样,并不一定在构造函数中。

什么是注入?

注入 在现实生活中顾明思议是 不属于某物体的对象放入某物体中,将 A物体 注入到 B物体,如:医生给打针、汽车加油、…。在软件工程 OOP 中也是同样道理,将某个 参数/类(外部资源) 注入到另一个个类中。
注入某个参数

// A 类
class A
{
    public function __construct($name)
    {
        // do something
    }
}

$a = new A('cxbdasheng');
// do something

注入某个类

// A 类
class A
{
    public function __construct($class)
    {
        // do something
    }
}

// B 类
class B
{
    public function __construct()
    {
        // do something
    }
}

$b = new B();
// 将 B 类注入给 A 类
$a = new A($b);
// do something

依赖注入

前面的 依赖注入 我们已经知道了,那么什么是 依赖注入 ?,依赖注入 其实是将 依赖注入 相结合。

// A 类
class A
{
    protected $b;
    public function __construct(B $class)
    {
        $this->b = $class;
        // do something
    }
}

// B 类
class B
{
    public function __construct()
    {
        // do something
    }
}

$b = new B();
// 将 B 类注入给 A 类
$a = new A($b);
// do something

控制反转

控制反转(Inversion of Control) 是一种设计思想,也可以说是 依赖倒置 子原则,我更愿意称它为一种思想,因为 SOLID 原则 并未包括它。我们前面介绍的 依赖注入 就是实现 控制反转 设计方法。网络上对:依赖倒置、控制反转、依赖注入这几种关系介绍模糊,我根据我对这几种关系的理解来谈谈。

  1. 依赖倒置【设计原则】
    看过设《设计模式》的同学应该对 SOLID 原则 都不陌生,其中 D(dependency Inversion Principle) 原则 也就是 依赖倒置 原则,依赖倒置原则说明:“高层次的类不应该依赖于低层次的类”。这是原则层描述。
  2. 控制反转【设计思想】
    控制反转 是一种 设计思想控制反转 并不指定哪种 设计实现 来实现,只要按照 控制反转 的思想去是实现,我们就称为 控制反转
  3. 依赖注入【设计实现】
    依赖注入,也是具体的实现,有原则和思想了总要去实现吧?也就是说 依赖注入是根据 原则(依赖倒置) 和 思想(控制反转) 来实现。根据 原则思想 去实现的还有 依赖查找,相比而言依赖注入是被动接收依赖对象,而 依赖查找主动查询依赖对象

我们可以把上层的 依赖倒置控制反转 比作接口类,而 依赖注入 则是按照一层层来实现的。
依赖倒置、控制反转、依赖注入关系如下图所示:
依赖倒置、控制反转、依赖注入的关系

我们继续介绍 控制反转

什么是控制?

现实中对一个物体,想让他干嘛就干嘛,这就是控制。在软件编程上,也就对一个对象实例,想让它干嘛就干嘛(创建、删除、调用),就称为 控制,如 A 类控制 B 类生成。

// A 类
class A
{
    public function __construct()
    {
        // A 控制 B 
        $b = new B();
        // do something
    }
}

// B 类
class B
{
    public function __construct()
    {
        return '我是 B 类';
    }
}

阿这……,这不就是前面依赖的例子吗?是的这个就是依赖的例子,只是在从 不同角度上描述 罢了,现在在思想层面描述,也就是 A 控制 B 。

什么是反转?

有反转那么肯定有正转,不然怎么要反转一下呢?

正转

A 的实例化需要 B,直接在 A 中去实例化 B 获取 B,叫做正转。也就是说只要在 A 中实例化 B 就叫正转(不管有没有执行 B 类中的方法)。
还是上面例子

// A 类
class A
{
    public function __construct()
    {
        // 正转
        $b = new B();
        // do something
    }
}

// B 类
class B
{
    public function __construct()
    {
        return '我是 B 类';
    }
}

反转

A 类不再主动去实例化 B,而是通过一个 第三方对象(IoC 容器),去 被动/主动 获取,等待 第三方对象 获取一个 B 的实例,叫做反转。第三方对象一般称为 IoC(Inversion of Control) 容器。

// B 类
class B
{
    public function __construct()
    {
        return '我是 B 类';
    }
}

// C 类
class C
{
    public function __construct()
    {
        return '我是 C 类';
    }
}

// IoC 容器
class IoC
{
    public function getInstance($className)
    {
        switch($className)
        {
            case 'b':
                return new B();
                break;
            case 'c':
                return new C();
                break;
            default:
                return null;
                break;
        }
    }
}

// A 类
class A
{
    public function __construct()
    {
        $ioc = new IoC();
        $b = $ioc->getInstance($className);
        // do something
    }
}

此处举例为依赖查找(主动去查找),依赖查找不止基于 Switch 实现,也可以使用 Php 的反射类,反转一般是在控制前提进行,代码就是描述的控制反转。

为什么需要反转

前面我们举 正转 的例子,代码已经写死,A 和 B 是直接耦合在一起的。如果以后因业务需求,类 B 的创建出现了一些问题,比如无法直接 new,构造器被隐藏设为私有等,业务人员改完 B 的代码,重新启动项目后发现 A 开始报错,又不得不去处理 A 的逻辑。我们只是想修改 B 的逻辑,但因为一些依赖关系又不得不去处理很多“原本不应该管的逻辑”。可以想象。这样的依赖关系如果多了的话,那后期维护代码会变得举步维艰。

A 和 B 的依赖中,A 只是想要 B 的服务。A 其实不需要管 B 的创建过程,只要有个 B 的对象来提供服务就好。

正是有了这个突破点,反转的概念顺势而出,即当类 A 与类 B 产生依赖关系时(A 需要 B),不需要 A 去主动创建 B,而是交给外界创建好 B 对象,然后通过一些方式把 B 对象交给 A 使用,这也称为 控制反转

控制反转 的意思是:将创建 B 这个行为的主动权从需要方类 A 的手中反转到其他人手中

只是外界创建好依赖对象 B 后,A 可以通过多种方式获取到 B。

通过依赖注入实现控制反转

资源基类接口
/**
 * 资源基类接口
 */
interface Resources
{
    /**
     * 使用资源
     * @return mixed
     * @author chendashengpc
     */
    public function use(): string;
}
资源具体类
/**
 * A 资源
 */
class A implements Resources
{
    /**
     * 使用 A 资源
     * @return string
     * @author chendashengpc
     */
    public function use(): string
    {
        return '现在使用的是 A 资源';
    }
}

/**
 * B 资源
 */
class B implements Resources
{
    /**
     * 使用 B 资源
     * @return string
     * @author chendashengpc
     */
    public function use(): string
    {
        return '现在使用的是 B 资源';
    }
}

/**
 * C 资源
 */
class C implements Resources
{
    /**
     * 使用 C 资源
     * @return string
     * @author chendashengpc
     */
    public function use():string
    {
        return '现在使用的是 C 资源';
    }
}
IoC 容器类

IoC 容器(switch 实现)

/**
 * IoC 容器(switch 实现)
 */
class Ioc
{
    /**
     * 获取指定资源
     * @param $resources 资源名
     * @return Resources
     * @author chendashengpc
     */
    public function getInstance($resources): Resources
    {
        switch ($resources) {
            case 'a':
                return new A();
                break;
            case 'b':
                return new B();
                break;
            case 'c':
                return new C();
                break;
            default:
                throw new \InvalidArgumentException('没有此类');
        }
    }
}

IoC 容器(反射实现)

use ReflectionClass;

/**
 * IoC 容器(反射实现)
 */
class ReflectorIoc
{
    /**
     * 获取指定资源
     * @param $resources 资源名
     * @return Resources
     * @author chendashengpc
     */
    public function getInstance($resources): Resources
    {
        // 转化成大写类名
        $class = strtoupper($resources);
        // 反射 双引号防止转义
        try {
            $reflector = new ReflectionClass(__NAMESPACE__ . '\\' . $class);
            // 调用反射类 newInstance 直接实例化了,资源类构造函数有传参需获取参数再进行实例化。
            $obj = $reflector->newInstance();
        } catch (\Exception $exception) {
            throw new \InvalidArgumentException('没有此类');
        }
        return $obj;
    }
}

注:IoC 容器(反射实现)中,资源类经反射后直接实例化,并未获取资源类的参数。如资源类构造函数有传参,则需获取参数再进行实例化。

注:尽管你使用了依赖注入,但也不一定能简单替换依赖。需要在平时编码就具有高抽象思维,这也是为什么提倡依赖接口而非实现,如果平时编码是面向过程思想,就算使用了依赖注入,替换依赖仍然会是一个巨大的工程。

客户端类
/**
 * 使用资源的客户端
 */
class Client
{
    public Resources $resources;

    public function __construct(Resources $resources)
    {
        $this->resources = $resources;
    }

    public function useResources()
    {
        echo $this->resources->use() . PHP_EOL;
    }
}

$ioc = new Ioc();
/**
 * 将资源类注入到 Client 类中
 */
$client = new Client($ioc->getInstance('b'));
$client->useResources();
$client = new Client($ioc->getInstance('c'));
$client->useResources();

$ioc = new ReflectorIoc();
/**
 * 将资源类注入到 Client 类中
 */
$client = new Client($ioc->getInstance('a'));
$client->useResources();

$client = new Client($ioc->getInstance('C'));
$client->useResources();

输出

现在使用的是 B 资源
现在使用的是 C 资源
现在使用的是 A 资源
现在使用的是 C 资源

参考 & 引用

分享到:
0

说点儿什么吧

头像

表情

本站由陈大剩博客程序搭建 | 湘ICP备2023000975号| Copyright © 2017 - 陈大剩博客 | 本站采用创作共用版权:CC BY-NC 4.0

站长统计| 文章总数[115]| 评论总数[9]| 登录用户[22]| 时间点[118]

logo

登入

社交账号登录