惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Tenable Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
V
Vulnerabilities – Threatpost
G
GRAHAM CLULEY
Simon Willison's Weblog
Simon Willison's Weblog
C
CXSECURITY Database RSS Feed - CXSecurity.com
P
Privacy International News Feed
H
Heimdal Security Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
S
Secure Thoughts
MyScale Blog
MyScale Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
V
Visual Studio Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
L
LINUX DO - 最新话题
D
Darknet – Hacking Tools, Hacker News & Cyber Security
The Cloudflare Blog
美团技术团队
Recorded Future
Recorded Future
T
Tailwind CSS Blog
Latest news
Latest news
Security Archives - TechRepublic
Security Archives - TechRepublic
Security Latest
Security Latest
Know Your Adversary
Know Your Adversary
Cloudbric
Cloudbric
Schneier on Security
Schneier on Security
I
Intezer
L
LINUX DO - 热门话题
P
Palo Alto Networks Blog
云风的 BLOG
云风的 BLOG
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Vercel News
Vercel News
Attack and Defense Labs
Attack and Defense Labs
人人都是产品经理
人人都是产品经理
L
LangChain Blog
爱范儿
爱范儿
博客园 - 三生石上(FineUI控件)
博客园 - 叶小钗
L
Lohrmann on Cybersecurity
S
SegmentFault 最新的问题
W
WeLiveSecurity
C
Cybersecurity and Infrastructure Security Agency CISA
S
Securelist
SecWiki News
SecWiki News
V2EX - 技术
V2EX - 技术
IT之家
IT之家
Cyberwarzone
Cyberwarzone
F
Full Disclosure
Spread Privacy
Spread Privacy
阮一峰的网络日志
阮一峰的网络日志

青空之蓝

[青空之蓝-2023] - 色彩 | 青空之蓝 [青空之蓝-2022] - 平静 | 青空之蓝 [青空之蓝-2021] - 远望 | 青空之蓝 浅谈垃圾回收 | 青空之蓝 浅谈泛型擦除 | 青空之蓝 浅谈单点登录 | 青空之蓝 使用 Kotlin 编写 Spring 测试 | 青空之蓝 设计模式系列文章 | 青空之蓝 从零实现一个 Java 微框架 - IoC | 青空之蓝 从零实现一个 Java 微框架 - 前言 | 青空之蓝 浅谈 JVM:类加载 | 青空之蓝 浅谈 IO | 青空之蓝 浅谈并发:synchronized & ReentrantLock | 青空之蓝 浅谈并发:CAS & AQS | 青空之蓝 浅谈并发:ThreadLocal | 青空之蓝 浅谈并发:三大特性 | 青空之蓝 浅谈组合注解 & 注解别名 | 青空之蓝 [青空之蓝-2020]-迷茫 | 青空之蓝 Java 系列文章 | 青空之蓝 HTTP 系列文章 | 青空之蓝 浅谈 EatWhatYouKill | 青空之蓝 浅谈可扩展线程池 | 青空之蓝 聊聊写框架 | 青空之蓝 聊聊现状-[2020-09] | 青空之蓝 浅谈并发:锁 | 青空之蓝 浅谈并发:基础 | 青空之蓝 浅谈缓存 | 青空之蓝 无须定义类,Spring 快速注入 Json 参数 | 青空之蓝 浅谈 Proxy 和 Aop | 青空之蓝 从零实现一个 PHP 微框架 - 初始化请求 | 青空之蓝 为 Vue3 添加一个简单的 Store | 青空之蓝 从零实现一个 PHP 微框架 - 服务提供者 | 青空之蓝 WSL2 踩坑记录 | 青空之蓝 浅谈浏览器Event Loop [更新] | 青空之蓝 从零实现一个 PHP 微框架 - Bootstrap 启动加载 | 青空之蓝 从零实现一个 PHP 微框架 - IoC 容器 | 青空之蓝 从零实现一个 PHP 微框架 - PSR & Composer | 青空之蓝 从零实现一个 PHP 微框架 - 前言 | 青空之蓝 MVVM 简单实现 | 青空之蓝 中间件实现 [PHP] | 青空之蓝 告别 Windows 终端的难看难用,打造好用的 PowerShell | 青空之蓝 VSCode Java输出中文乱码问题解决[更新] | 青空之蓝 浅谈浏览器渲染 | 青空之蓝 Vue-Cli@2 项目迁移日志 | 青空之蓝 Laragon & Scoop 集成踩坑记录 | 青空之蓝 「一行代码」优雅管理 Windows 软件 | 青空之蓝 [青空之蓝-2019]-年度总结 | 青空之蓝 为Vue添加简单的Store | 青空之蓝 为React添加简单的Store | 青空之蓝 为Vuex添加同步Action | 青空之蓝 浅谈B+树 | 青空之蓝 浅谈跳表 | 青空之蓝 浅谈数据库索引 | 青空之蓝 MySQL事务隔离 | 青空之蓝 算法复杂度分析(1) | 青空之蓝 一年来的经验总结 | 青空之蓝 Acrylic - VSCode Extension | 青空之蓝 ace编辑器设置惯性滚动 | 青空之蓝 Java二叉树实现 | 青空之蓝 Java图实现 | 青空之蓝 为apt方式安装的nginx重新编译增加WebDAV | 青空之蓝 XK-Editor - 一个支持富文本和Markdown的编辑器 | 青空之蓝 JS生成列表树 | 青空之蓝 Laravel生成目录树 | 青空之蓝 XK-Note - 集各种神奇功能的云笔记 | 青空之蓝 PHP GD生成验证码 | 青空之蓝 PHP GD图片处理[转换格式-水印-缩略图] | 青空之蓝 Origami - 简洁轻快的WordPress主题 | 青空之蓝 为WordPress启用WorkBox | 青空之蓝 [青空之蓝-2018]-年度总结 | 青空之蓝 VSCode Java手动导入jar和源码包 | 青空之蓝 Windows IP变化自动发送邮件 | 青空之蓝 C链表实现重制版 | 青空之蓝 C 结构体的定义和使用 | 青空之蓝 图的搜索(遍历) - BFS & DFS | 青空之蓝 Java链表实现 | 青空之蓝 C 快速排序 | 青空之蓝 C 插入排序 | 青空之蓝 C 归并排序 | 青空之蓝 C语言链表实现 | 青空之蓝 VSCode配置Java调试环境[Windows] | 青空之蓝 C 选择排序 | 青空之蓝 C 冒泡排序 | 青空之蓝 VSCode配置PHP调试环境[Windows] | 青空之蓝 VSCode配置C/C++ GDB调试环境[Windows] | 青空之蓝 WordPress友情链接模板 | 青空之蓝 Intel Optane 傲腾内存体验 | 青空之蓝 Mysql双机热备实战 | 青空之蓝 博客一年记录 | 青空之蓝 为WordPress启用Service Worker | 青空之蓝 Bing每日一图API | 青空之蓝 iframe延迟加载 | 青空之蓝 写在2018年高考前 | 青空之蓝 The Fox主题汉化分享 | 青空之蓝 [青空之蓝-2017]-崭新 | 青空之蓝 本博客评论规则 | 青空之蓝 世界,您好! | 青空之蓝
浅谈 DI 和 IoC | 青空之蓝
Otstar Lin · 2020-04-09 · via 青空之蓝

前言

用过 Laravel 或者 Spring 的人一定都听过 依赖注入(DI)控制反转(IoC) 这个概念,对于刚接触这两个个概念的人来说,这两个概念很难理解,正好最近折腾了下设计模式把这两个概念给补上了,于是便打算写一篇文章记录下。

控制反转是什么?

用单纯的语言肯定讲不清楚这个概念的,所以,我们先来看一段代码吧:

<?php
interface Device
{
    public function playGame();
}

class Computer implements Device
{
    public function playGame()
    {
        echo "玩游戏\n";
    }
}

class Player
{
    private $name = "玩家";

    public function printName()
    {
        echo $this->name . "\n";
        return $this;
    }

    public function playGame()
    {
        (new Computer)->playGame();
        return $this;
    }
}

(new Player)->printName()->playGame();

// 玩家
// 玩游戏
<?php
interface Device
{
    public function playGame();
}

class Computer implements Device
{
    public function playGame()
    {
        echo "玩游戏\n";
    }
}

class Player
{
    private $name = "玩家";

    public function printName()
    {
        echo $this->name . "\n";
        return $this;
    }

    public function playGame()
    {
        (new Computer)->playGame();
        return $this;
    }
}

(new Player)->printName()->playGame();

// 玩家
// 玩游戏

首先我们先看一下上面的代码,玩家要玩游戏,但是玩家并没办法仅靠自己完成这个操作,需要借助一台电脑,此时电脑就称之为 依赖,而玩家操作电脑玩游戏的过程就称之为 控制

不过随着科技的进步,手机游戏逐渐的取代了部分桌面游戏,该玩家也因为各种广告的熏陶逐渐想玩某款手机游戏了,不过玩家发现自己已经和电脑 "绑定" 在一起了,如果要更换设备就需要修改内部的代码。不过这样以后就不能再使用电脑,作为一个重度电脑依赖的玩家这怎么能接受。

那么有什么方法可以解决这个问题吗?有的,那就是把 控制反转 过来,将 依赖,也就是设备动态的赋予玩家,玩家不控制设备的创建和销毁,玩家只负责使用设备,如下代码:

<?php
interface Device
{
    public function playGame();
}

class Computer implements Device
{
    public function playGame()
    {
        echo "玩游戏\n";
    }
}

class Player
{
    private $name = "玩家";
    private $device;

    public function __construct(Device $device)
    {
        $this->device = $device;
    }

    public function printName()
    {
        echo $this->name . "\n";
        return $this;
    }

    public function playGame()
    {
        $this->device->playGame();
        return $this;
    }
}

(new Player(new Computer))->printName()->playGame();

// 玩家
// 玩游戏
<?php
interface Device
{
    public function playGame();
}

class Computer implements Device
{
    public function playGame()
    {
        echo "玩游戏\n";
    }
}

class Player
{
    private $name = "玩家";
    private $device;

    public function __construct(Device $device)
    {
        $this->device = $device;
    }

    public function printName()
    {
        echo $this->name . "\n";
        return $this;
    }

    public function playGame()
    {
        $this->device->playGame();
        return $this;
    }
}

(new Player(new Computer))->printName()->playGame();

// 玩家
// 玩游戏

依赖注入是什么?

从上面的代码可以看到设备是动态传入的,当需要更换设备的时候就只需要更换传入的参数即可,而不需要修改玩家类,而将参数传入到类的过程就称之为 依赖注入

常见的依赖注入的方式有三种:

  1. 构造器注入
  2. 接口注入
  3. 设置属性注入

上方代码使用的就是 构造器注入 的方式。

依赖注入可以解决依赖需要切换的问题,解决各部件之间的强耦合问题。但是依赖注入也有个麻烦,如果依赖项太多了怎么办?每次使用的时候都需要创建一堆的依赖,并设置这些依赖:

<?php
$c1 = new Class1;
$c2 = new Class2;
$c3 = new Class3;
$c4 = new Class4;
$c5 = new Class5;

$c6 = new Class6($c1, $c2, $c3, $c4, $c5);
<?php
$c1 = new Class1;
$c2 = new Class2;
$c3 = new Class3;
$c4 = new Class4;
$c5 = new Class5;

$c6 = new Class6($c1, $c2, $c3, $c4, $c5);

可以看到这样的代码并不优雅,还会加大对象创建的难度和复杂度,这种情况可以使用工厂模式来解决:

<?php
class Factory {
    public static function make()
    {
        //... new class 1-5
        return new Class6($c1, $c2, $c3, $c4, $c5);
    }
}

$c6 = Factory::make();
<?php
class Factory {
    public static function make()
    {
        //... new class 1-5
        return new Class6($c1, $c2, $c3, $c4, $c5);
    }
}

$c6 = Factory::make();

工厂模式解决了创建对象复杂的问题,但是你会发现,这样就又回到了原点,对象的依赖就被工厂绑定了,当需要使用到其他依赖的时候工厂就无用武之地了。

IoC 容器

那么还有什么的其他的方法能解决依赖注入的问题吗?答案是使用 IoC 容器。那么什么是 IoC 容器呢?我们可以把 IoC 容器 看成一个 仓库,里面存放着 依赖项,在运行的时候 注册绑定 一些需要用到的依赖,当我们要使用的某个对象的时候,直接 提取制造 一个出来即可,容器内部会自动创建并注入依赖。

反射

反射是 IoC 容器实现的关键,通过反射 IoC 容器就能知道类或者方法用到了哪些依赖,知道了需要使用的依赖后就可以到依赖列表中进行查找查找完成后就可以将依赖赋值给对应的类和方法。

举个例子说明下吧:

<?php
class Cat
{
    public function getName(): string
    {
        return "";
    }
}

class PetShop
{
    /**
     * 动物依赖
     *
     * @var Cat
     */
    private $cat;

    /**
     * 构造器注入
     *
     * @param   Cat  $cat  动物依赖
     *
     * @return  PetShop
     */
    public function __construct(Cat $cat)
    {
        $this->cat = $cat;
    }

    public function printName()
    {
        echo $this->cat->getName() . "\n";
    }
}

// 获取反射类
$reflector = new ReflectionClass(PetShop::class);
// 获取构造器
$constructor = $reflector->getConstructor();
// 获取构造器的参数,也就是依赖
$dependencies = $constructor->getParameters();
// 打印所有的依赖
var_dump($dependencies);
// 获取依赖的类
$class = $dependencies[0]->getClass();
var_dump($class);
// 创建依赖对象
$cat = new $class->name;
var_dump($cat);
// 将依赖注入,并实例化
$pet_shop = $reflector->newInstanceArgs([$cat]);
$pet_shop->printName();
// F:\Code\code\design-pattern\Test.php:44:
// array(1) {
//   [0] =>
//   class ReflectionParameter#3 (1) {
//     public $name =>
//     string(3) "cat"
//   }
// }
// F:\Code\code\design-pattern\Test.php:47:
// class ReflectionClass#4 (1) {
//   public $name =>
//   string(3) "Cat"
// }
// F:\Code\code\design-pattern\Test.php:50:
// class Cat#5 (0) {
// }
// 猫
<?php
class Cat
{
    public function getName(): string
    {
        return "";
    }
}

class PetShop
{
    /**
     * 动物依赖
     *
     * @var Cat
     */
    private $cat;

    /**
     * 构造器注入
     *
     * @param   Cat  $cat  动物依赖
     *
     * @return  PetShop
     */
    public function __construct(Cat $cat)
    {
        $this->cat = $cat;
    }

    public function printName()
    {
        echo $this->cat->getName() . "\n";
    }
}

// 获取反射类
$reflector = new ReflectionClass(PetShop::class);
// 获取构造器
$constructor = $reflector->getConstructor();
// 获取构造器的参数,也就是依赖
$dependencies = $constructor->getParameters();
// 打印所有的依赖
var_dump($dependencies);
// 获取依赖的类
$class = $dependencies[0]->getClass();
var_dump($class);
// 创建依赖对象
$cat = new $class->name;
var_dump($cat);
// 将依赖注入,并实例化
$pet_shop = $reflector->newInstanceArgs([$cat]);
$pet_shop->printName();
// F:\Code\code\design-pattern\Test.php:44:
// array(1) {
//   [0] =>
//   class ReflectionParameter#3 (1) {
//     public $name =>
//     string(3) "cat"
//   }
// }
// F:\Code\code\design-pattern\Test.php:47:
// class ReflectionClass#4 (1) {
//   public $name =>
//   string(3) "Cat"
// }
// F:\Code\code\design-pattern\Test.php:50:
// class Cat#5 (0) {
// }
// 猫

可以看到代码中通过 PetShop 的反射类获取到了构造器的参数列表,并且通过反射参数获取到参数的类名,此时就可以通过类名创建对应的依赖,最后再使用反射类实例化出对象。

容器代码

首先先贴代码:https://github.com/syfxlin/code/tree/master/design-pattern/IoC

这个 IoC 容器是给 XK-PHP 设计的,参考了 Laravel 的容器和网上一些代码改造而来,实现了大部分 Laravel 容器的功能,所以显得很复杂 2333。这里就大致讲一下流程。

容器创建后就需要调用 bind 方法绑定依赖,bind 方法再通过调用 setBind 方法完成对依赖的绑定,在 setBind 中会判断传入的 concrete 是否是闭包,如果不是闭包则 创建 一个闭包,之所以使用闭包而不是直接 new 一个依赖是为了达到懒加载的效果,如果直接 new 一个依赖其实就变成了单例模式了,单例模式的实例是不会变化的这样也就没必要使用容器了。

完成依赖的绑定后,当我们需要使用到某个对象的时候,就需要调用 make 方法,make 方法会 运行 对应对象的闭包,闭包的返回值就是当前对象的实例,这个闭包可以自行设置,也可以使用 默认的闭包,在默认的闭包中会调用 build 方法来构建实例,构建的流程其实就是上面反射一节中的流程,在 build 方法中会调用 injectingDependencies 方法来处理要注入的依赖参数。若对应的依赖通过是通过外部传入的则直接进行 设置。若需要的依赖是一个类,则调用 resolveClass 方法来处理类依赖,由于依赖可能还需要使用到其他依赖,所以需要调用 make 方法来进行递归处理依赖。如果需要的依赖不是一个类,则通过 resolvePrimitive 方法来处理依赖,查找的方式是使用变量名的方式,如果容器中有绑定对应变量名的依赖则 注入 对应的绑定,如果没找到绑定的变量,那么就看看有没有 默认值,如果默认值也没有,那么就抛出异常。如果一切正常那么最终就会 实例化 对应的对象。

当然,有依赖注入类的,自然也有注入函数或方法的,具体这里就不讲了,过程和注入类类似,相关的代码见 Github

结语

总算写完了,又是一篇水文呢 o(^▽^)o,不知道大家有没有看懂呢?文中可能有错误或不足之处,如果您发现了问题欢迎反馈。[]~( ̄ ▽  ̄)~*

浅谈 DI 和 IoC

https://blog.ixk.me/post/talking-about-di-and-ioc
  • 许可协议

    BY-NC-SA

  • 本文作者

    Otstar Lin

  • 发布于

    2020/04/09

转载或引用本文时请遵守许可协议,注明出处、不得用于商业用途!

MVVM 简单实现中间件实现 [PHP]