0

0

依赖注入(DI)在PHP中的实现

php中文网

php中文网

发布时间:2016-06-20 12:29:12

|

1091人浏览过

|

来源于php中文网

原创

什么是依赖注入?

IOC:英文全称:Inversion of Control,中文名称:控制反转,它还有个名字叫依赖注入(Dependency Injection,简称DI)。

当一个类的实例需要另一个类的实例协助时,在传统的程序设计过程中,通常由调用者来创建被调用者的实例。而采用依赖注入的方式,创建被调用者的工作不再由调用者来完成,因此叫控制反转,创建被调用者的实例的工作由IOC容器来完成,然后注入调用者,因此也称为依赖注入。

举个简单的例子:

(1)原始社会里,几乎没有社会分工。需要斧子的人(调用者)只能自己去磨一把斧子(被调用者)。

立即学习PHP免费学习笔记(深入)”;

(2)进入工业社会,工厂出现。斧子不再由普通人完成,而在工厂里被生产出来,此时需要斧子的人(调用者)找到工厂,购买斧子,无须关心斧子的制造过程。

(3)进入“按需分配”社会,需要斧子的人不需要找到工厂,坐在家里发出一个简单指令:需要斧子。斧子就自然出现在他面前。

第一种情况下,实例的调用者创建被调用的实例,必然要求被调用的类出现在调用者的代码里。无法实现二者之间的松耦合。

第二种情况下,调用者无须关心被调用者具体实现过程,只需要找到符合某种标准(接口)的实例,即可使用。此时调用的代码面向接口编程,可以让调用者和被调用者解耦,这也是工厂模式大量使用的原因。但调用者需要自己定位工厂,调用者与特定工厂耦合在一起。

第三种情况下,调用者无须自己定位工厂,程序运行到需要被调用者时,依赖注入容器自动提供被调用者实例。事实上,调用者和被调用者都处于依赖注入容器的管理下,二者之间的依赖关系由依赖注入容器提供。因此调用者与被调用者的耦合度进一步降低,这使得应用更加容易维护,这就是依赖注入所要达到的目的。

用php实现一个轻量的依赖注入容器

首先我们创建一个类,看起来是这样的:

<?php   class Di{    protected $_service = [];    public function set($name, $definition)    {        $this->_service[$name] = $definition;    }    public function get($name)    {        if (isset($this->_service[$name])) {            $definition = $this->service[$name];        } else {            throw new Exception("Service '" . name . "' wasn't found in the dependency injection container");        }        if (is_object($definition)) {            $instance = call_user_func($definition);        }        return $instance;    }}

现在我们已经有了一个简单的类,包含一个属性和两个方法。假设我们现在有两个类,redisDB和cache,redisDB提供一个redis数据库的操作,cache负责缓存功能的实现并且依赖于redisDB。

PHP5 和 MySQL 圣经
PHP5 和 MySQL 圣经

本书是全面讲述PHP与MySQL的经典之作,书中不但全面介绍了两种技术的核心特性,还讲解了如何高效地结合这两种技术构建健壮的数据驱动的应用程序。本书涵盖了两种技术新版本中出现的最新特性,书中大量实际的示例和深入的分析均来自于作者在这方面多年的专业经验,可用于解决开发者在实际中所面临的各种挑战。

下载
class redisDB{    protected $_di;    protected $_options;    public function __construct($options = null)    {        $this->_options = $options;    }    public function setDI($di)    {        $this->_di = $di;    }    public function find($key, $lifetime)    {        // code    }    public function save($key, $value, $lifetime)    {        // code    }    public function delete($key)    {        // code    }}

在这个类中我们简单实现了redis的查询、保存和删除。你可能会有疑问,另外一个方法setDi是做什么的。待我继续为你讲解。另一个类和当前这个类结构很像:

class cache{    protected $_di;    protected $_options;    protected $_connect;    public function __construct($options = null)    {        $this->_options = $options;    }    public function setDI($di)    {        $this->_di = $di;    }    protected function _connect()    {        $options = $this->_options;        if (isset($options['connect'])) {            $service = $options['connect'];        } else {            $service = 'redis';        }        return $this->_di->get($service);    }    public function get($key, $lifetime)    {        $connect = $this->_connect;        if (!is_object($connect)) {            $connect = $this->_connect()            $this->_connect = $connect;        }        // code        ...        return $connect->find($key, $lifetime);    }    public function save($key, $value, $lifetime)    {        $connect = $this->_connect;        if (!is_object($connect)) {            $connect = $this->_connect()            $this->_connect = $connect;        }        // code        ...        return $connect->save($key, $lifetime);    }    public function delete($key)    {        $connect = $this->_connect;        if (!is_object($connect)) {            $connect = $this->_connect()            $this->_connect = $connect;        }        // code        ...        $connect->delete($key, $lifetime);    }}

现在我们就当已经实现了redisDB和cache这两个组件,具体的细节这里就先不做讨论了,来看看如何使用使用吧。首先需要将两个组件注入到容器中:

<?php    $di = new Di();    $di->set('redis', function() {         return new redisDB([             'host' => '127.0.0.1',             'port' => 6379         ]);    });    $di->set('cache', function() use ($di) {        $cache = new cache([            'connect' => 'redis'        ]);        $cache->setDi($di);        return $cache;    });    // 然后在任何你想使用cache的地方    $cache = $di->get('cache');    $cache->get('key'); // 获取缓存数据    $cache->save('key', 'value', 'lifetime'); // 保存数据    $cache->delete('key'); // 删除数据

到这里你可能会觉得这样以来反而有点繁琐了。cache和redisDB的结构如此之像,完全可以把redis写到cache中而没必要单独分离出来?但是你想过没有,有些数据及时性没那么高而且数量比较大,用redis有点不合适,mongodb是更好的选择;有些数据更新频率更慢,对查询速度也没要求,直接写入文件保存到硬盘可能更为合适;再或者,你的客户觉得redis运维难度有点大,让你给他换成memcache... 这就是为什么把它分离出来了。然后,继续改进代码:

interface BackendInterface {    public function find($key, $lifetime);    public function save($key, $value, $lifetime);    public function delete($key);}class redisDB implements BackendInterface{    public function find($key, $lifetime) { }    public function save($key, $value, $lifetime) { }    public function delete($key) { }}class mongoDB implements BackendInterface{    public function find($key, $lifetime) { }    public function save($key, $value, $lifetime) { }    public function delete($key) { }}class file implements BackendInterface{    public function find($key, $lifetime) { }    public function save($key, $value, $lifetime) { }    public function delete($key) { }}$di = new Di();//  redis$di->set('redis', function() {     return new redisDB([         'host' => '127.0.0.1',         'port' => 6379     ]);});// mongodb$di->set('mongo', function() {     return new mongoDB([         'host' => '127.0.0.1',         'port' => 12707     ]);});// file$di->set('file', function() {     return new file([         'path' => 'path'     ]);});// save at redis$di->set('fastCache', function() use ($di) {     $cache = new cache([         'connect' => 'redis'     ]);     $cache->setDi($di);     return $cache;});// save at mongodb$di->set('cache', function() use ($di) {     $cache = new cache([         'connect' => 'mongo'     ]);     $cache->setDi($di);     return $cache;});// save at file$di->set('slowCache', function() use ($di) {     $cache = new cache([         'connect' => 'file'     ]);     $cache->setDi($di);     return $cache;});// 然后在任何你想使用cache的地方 $cache = $di->get('cache');

我们新增加了一个接口BackendInterface,规定了redisDB,mongoDB,file这三个类必须实现这个接口所要求的功能,至于其他锦上添花的功能,随你怎么发挥。而cache的代码,好像没有变,因为cache不需要关心数据是怎么存入数据库或者文件中。而cache的调用者,也不需要关心cache具体是怎么实现的,只要根据接口实现相应的方法就行了。多人协作你会更加受益,你们只需要商定好接口,然后分别实现就行了。

这就是依赖注入的魅力所在了,虽然看似如此简单。

以上代码还可以继续改进,直到你认为无可挑剔为止。比如,redis服务在一个请求中会调用多次,而每次调用都会重新创建,这将有损性能。只需扩展一下DI容器就好怎加一个参数或者怎加一个方法,随你。

class Di{    protected $_service = [];    protected $_sharedService = [];    public function set($name, $definition, $shared = false)    {        if ($shared) {            $this->_sharedService[$name] = $definition;        } else {            $this->_service[$name] = $definition;        }    }    public function get($name) {        if (isset($this->_service[$name])) {            $definition = $this->service[$name];        } else if ($this->_sharedService[$name]) {             $definition = $this->_sharedService[$name];        } else {            throw new Exception("Service '" . name . "' wasn't found in the dependency injection container");        }        ...    }

这样以来,如果某个服务在一次请求中要调用多次,你就可以将shared属性设置为true,以减少不必要的浪费。如果你觉得每次在注入时都要setDi有点繁琐,想让他自动setDi,那可以这么做:

interface DiAwareInterface{    public function setDI($di);    public function getDI();}class Di{    protected $service;    public function set($name, $definition)    {        $this->service[$name] = $definition;    }    public function get($name)    {        ...        if (is_object($definition)) {            $instance = call_user_func($definition);        }        // 如果实现了DiAwareInterface这个接口,自动注入        if (is_object($instance)) {            if ($instance instanceof DiAwareInterface) {                $instance->setDI($this);            }        }        return $instance;    }}class redisDB implements BackendInterface, DiAwareInterface{    public function find($key, $lifetime) { }    public function save($key, $value, $lifetime) { }    public function delete($key) { }}

然后,就可以这样:

$di->set('cache', function() {    return new cache([        'connect' => 'mongo'    ]);});

我们现在所实现的这个DI容器还很简陋,还不支持复杂的注入,你可以继续完善它。

不过,通过这些代码你已经了解什么是依赖在注入了,你可以将这种思想应用到你的项目中,或者着手开发你自己的框架。如果想继续深入学习的话,烦请 click Scene。

完。

相关文章

PHP速学教程(入门到精通)
PHP速学教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
Kotlin Android模块化架构与组件化开发实践
Kotlin Android模块化架构与组件化开发实践

本专题围绕 Kotlin 在 Android 应用开发中的架构实践展开,重点讲解模块化设计与组件化开发的实现思路。内容包括项目模块拆分策略、公共组件封装、依赖管理优化、路由通信机制以及大型项目的工程化管理方法。通过真实项目案例分析,帮助开发者构建结构清晰、易扩展且维护成本低的 Android 应用架构体系,提升团队协作效率与项目迭代速度。

6

2026.03.09

JavaScript浏览器渲染机制与前端性能优化实践
JavaScript浏览器渲染机制与前端性能优化实践

本专题围绕 JavaScript 在浏览器中的执行与渲染机制展开,系统讲解 DOM 构建、CSSOM 解析、重排与重绘原理,以及关键渲染路径优化方法。内容涵盖事件循环机制、异步任务调度、资源加载优化、代码拆分与懒加载等性能优化策略。通过真实前端项目案例,帮助开发者理解浏览器底层工作原理,并掌握提升网页加载速度与交互体验的实用技巧。

60

2026.03.06

Rust内存安全机制与所有权模型深度实践
Rust内存安全机制与所有权模型深度实践

本专题围绕 Rust 语言核心特性展开,深入讲解所有权机制、借用规则、生命周期管理以及智能指针等关键概念。通过系统级开发案例,分析内存安全保障原理与零成本抽象优势,并结合并发场景讲解 Send 与 Sync 特性实现机制。帮助开发者真正理解 Rust 的设计哲学,掌握在高性能与安全性并重场景中的工程实践能力。

148

2026.03.05

PHP高性能API设计与Laravel服务架构实践
PHP高性能API设计与Laravel服务架构实践

本专题围绕 PHP 在现代 Web 后端开发中的高性能实践展开,重点讲解基于 Laravel 框架构建可扩展 API 服务的核心方法。内容涵盖路由与中间件机制、服务容器与依赖注入、接口版本管理、缓存策略设计以及队列异步处理方案。同时结合高并发场景,深入分析性能瓶颈定位与优化思路,帮助开发者构建稳定、高效、易维护的 PHP 后端服务体系。

273

2026.03.04

AI安装教程大全
AI安装教程大全

2026最全AI工具安装教程专题:包含各版本AI绘图、AI视频、智能办公软件的本地化部署手册。全篇零基础友好,附带最新模型下载地址、一键安装脚本及常见报错修复方案。每日更新,收藏这一篇就够了,让AI安装不再报错!

113

2026.03.04

Swift iOS架构设计与MVVM模式实战
Swift iOS架构设计与MVVM模式实战

本专题聚焦 Swift 在 iOS 应用架构设计中的实践,系统讲解 MVVM 模式的核心思想、数据绑定机制、模块拆分策略以及组件化开发方法。内容涵盖网络层封装、状态管理、依赖注入与性能优化技巧。通过完整项目案例,帮助开发者构建结构清晰、可维护性强的 iOS 应用架构体系。

159

2026.03.03

C++高性能网络编程与Reactor模型实践
C++高性能网络编程与Reactor模型实践

本专题围绕 C++ 在高性能网络服务开发中的应用展开,深入讲解 Socket 编程、多路复用机制、Reactor 模型设计原理以及线程池协作策略。内容涵盖 epoll 实现机制、内存管理优化、连接管理策略与高并发场景下的性能调优方法。通过构建高并发网络服务器实战案例,帮助开发者掌握 C++ 在底层系统与网络通信领域的核心技术。

30

2026.03.03

Golang 测试体系与代码质量保障:工程级可靠性建设
Golang 测试体系与代码质量保障:工程级可靠性建设

Go语言测试体系与代码质量保障聚焦于构建工程级可靠性系统。本专题深入解析Go的测试工具链(如go test)、单元测试、集成测试及端到端测试实践,结合代码覆盖率分析、静态代码扫描(如go vet)和动态分析工具,建立全链路质量监控机制。通过自动化测试框架、持续集成(CI)流水线配置及代码审查规范,实现测试用例管理、缺陷追踪与质量门禁控制,确保代码健壮性与可维护性,为高可靠性工程系统提供质量保障。

81

2026.02.28

Golang 工程化架构设计:可维护与可演进系统构建
Golang 工程化架构设计:可维护与可演进系统构建

Go语言工程化架构设计专注于构建高可维护性、可演进的企业级系统。本专题深入探讨Go项目的目录结构设计、模块划分、依赖管理等核心架构原则,涵盖微服务架构、领域驱动设计(DDD)在Go中的实践应用。通过实战案例解析接口抽象、错误处理、配置管理、日志监控等关键工程化技术,帮助开发者掌握构建稳定、可扩展Go应用的最佳实践方法。

62

2026.02.28

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
进程与SOCKET
进程与SOCKET

共6课时 | 0.4万人学习

PHP自制框架
PHP自制框架

共8课时 | 0.6万人学习

c语言项目php解释器源码分析探索
c语言项目php解释器源码分析探索

共7课时 | 0.4万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号