理解 PHP 依赖注入
Laravel框架的依赖注入确实很强大,并且通过容器实现依赖注入可以有选择性的加载需要的服务,减少初始化框架的开销,下面是我在网上看到的一个帖子,写的很好拿来与大家分享,文章从开始按照传统的类设计数据库连接一直到通过容器加载服务这个高度解耦的设计展示了依赖注入的强大之处,值得我们借鉴和学习。
-----------------------------------------------------------分割线下面是大牛的原文----------------------------------------------------------
首先,我们假设,我们要开发一个组件命名为SomeComponent。这个组件中现在将要注入一个数据库连接。在这个例子中,数据库连接在component中被创建,这种方法是不切实际的,这样做的话,我们将不能改变数据库连接参数及数据库类型等一些参数。
1 <?php
2
3 class SomeComponent
4 {
5
6 /**
7 * The instantiation of the connection is hardcoded inside
8 * the component so is difficult to replace it externally
9 * or change its behavior
10 */
11 public function someDbTask()
12 {
13 $connection = new Connection(array(
14 "host" => "localhost",
15 "username" => "root",
16 "password" => "secret",
17 "dbname" => "invo"
18 ));
19
20 // ...
21 }
22
23 }
24
25 $some = new SomeComponent();
26 $some->someDbTask();
为了解决上面所说的问题,我们需要在使用前创建一个外部连接,并注入到容器中。就目前而言,这看起来是一个很好的解决方案:
1 <?php
2
3 class SomeComponent
4 {
5
6 protected $_connection;
7
8 /**
9 * Sets the connection externally
10 */
11 public function setConnection($connection)
12 {
13 $this->_connection = $connection;
14 }
15
16 public function someDbTask()
17 {
18 $connection = $this->_connection;
19
20 // ...
21 }
22
23 }
24
25 $some = new SomeComponent();
26
27 //Create the connection
28 $connection = new Connection(array(
29 "host" => "localhost",
30 "username" => "root",
31 "password" => "secret",
32 "dbname" => "invo"
33 ));
34
35 //Inject the connection in the component
36 $some->setConnection($connection);
37
38 $some->someDbTask();
现在我们来考虑一个问题,我们在应用程序中的不同地方使用此组件,将多次创建数据库连接。使用一种类似全局注册表的方式,从这获得一个数据库连接实例,而不是使用一次就创建一次。
1 <?php
2
3 class Registry
4 {
5
6 /**
7 * Returns the connection
8 */
9 public static function getConnection()
10 {
11 return new Connection(array(
12 "host" => "localhost",
13 "username" => "root",
14 "password" => "secret",
15 "dbname" => "invo"
16 ));
17 }
18
19 }
20
21 class SomeComponent
22 {
23
24 protected $_connection;
25
26 /**
27 * Sets the connection externally
28 */
29 public function setConnection($connection){
30 $this->_connection = $connection;
31 }
32
33 public function someDbTask()
34 {
35 $connection = $this->_connection;
36
37 // ...
38 }
39
40 }
41
42 $some = new SomeComponent();
43
44 //Pass the connection defined in the registry
45 $some->setConnection(Registry::getConnection());
46
47 $some->someDbTask();
现在,让我们来想像一下,我们必须在组件中实现两个方法,首先需要创建一个新的数据库连接,第二个总是获得一个共享连接:
1 <?php
2
3 class Registry
4 {
5
6 protected static $_connection;
7
8 /**
9 * Creates a connection
10 */
11 protected static function _createConnection()
12 {
13 return new Connection(array(
14 "host" => "localhost",
15 "username" => "root",
16 "password" => "secret",
17 "dbname" => "invo"
18 ));
19 }
20
21 /**
22 * Creates a connection only once and returns it
23 */
24 public static function getSharedConnection()
25 {
26 if (self::$_connection===null){
27 $connection = self::_createConnection();
28 self::$_connection = $connection;
29 }
30 return self::$_connection;
31 }
32
33 /**
34 * Always returns a new connection
35 */
36 public static function getNewConnection()
37 {
38 return self::_createConnection();
39 }
40
41 }
42
43 class SomeComponent
44 {
45
46 protected $_connection;
47
48 /**
49 * Sets the connection externally
50 */
51 public function setConnection($connection){
52 $this->_connection = $connection;
53 }
54
55 /**
56 * This method always needs the shared connection
57 */
58 public function someDbTask()
59 {
60 $connection = $this->_connection;
61
62 // ...
63 }
64
65 /**
66 * This method always needs a new connection
67 */
68 public function someOtherDbTask($connection)
69 {
70
71 }
72
73 }
74
75 $some = new SomeComponent();
76
77 //This injects the shared connection
78 $some->setConnection(Registry::getSharedConnection());
79
80 $some->someDbTask();
81
82 //Here, we always pass a new connection as parameter
83 $some->someOtherDbTask(Registry::getConnection());
到此为止,我们已经看到了如何使用依赖注入解决我们的问题。不是在代码内部创建依赖关系,而是让其作为一个参数传递,这使得我们的程序更容易维护,降低程序代码的耦合度,实现一种松耦合。但是从长远来看,这种形式的依赖注入也有一些缺点。
例如,如果组件中有较多的依赖关系,我们需要创建多个setter方法传递,或创建构造函数进行传递。另外,每次使用组件时,都需要创建依赖组件,使代码维护不太易,我们编写的代码可能像这样:
1 <?php
2
3 //Create the dependencies or retrieve them from the registry
4 $connection = new Connection();
5 $session = new Session();
6 $fileSystem = new FileSystem();
7 $filter = new Filter();
8 $selector = new Selector();
9
10 //Pass them as constructor parameters
11 $some = new SomeComponent($connection, $session, $fileSystem, $filter, $selector);
12
13 // ... or using setters
14
15 $some->setConnection($connection);
16 $some->setSession($session);
17 $some->setFileSystem($fileSystem);
18 $some->setFilter($filter);
19 $some->setSelector($selector);
我想,我们不得不在应用程序的许多地方创建这个对象。如果你不需要依赖的组件后,我们又要去代码注入部分移除构造函数中的参数或者是setter方法。为了解决这个问题,我们再次返回去使用一个全局注册表来创建组件。但是,在创建对象之前,它增加了一个新的抽象层:
1 <?php
2
3 class SomeComponent
4 {
5
6 // ...
7
8 /**
9 * Define a factory method to create SomeComponent instances injecting its dependencies
10 */
11 public static function factory()
12 {
13
14 $connection = new Connection();
15 $session = new Session();
16 $fileSystem = new FileSystem();
17 $filter = new Filter();
18 $selector = new Selector();
19
20 return new self($connection, $session, $fileSystem, $filter, $selector);
21 }
22
23 }
这一刻,我们好像回到了问题的开始,我们正在创建组件内部的依赖,我们每次都在修改以及找寻一种解决问题的办法,但这都不是很好的做法。
一种实用和优雅的来解决这些问题,是使用容器的依赖注入,像我们在前面看到的,容器作为全局注册表,使用容器的依赖注入做为一种桥梁来解决依赖可以使我们的代码耦合度更低,很好的降低了组件的复杂性:
1 <?php
2
3 class SomeComponent
4 {
5
6 protected $_di;
7
8 public function __construct($di)
9 {
10 $this->_di = $di;
11 }
12
13 public function someDbTask()
14 {
15
16 // Get the connection service
17 // Always returns a new connection
18 $connection = $this->_di->get('db');
19
20 }
21
22 public function someOtherDbTask()
23 {
24
25 // Get a shared connection service,
26 // this will return the same connection everytime
27 $connection = $this->_di->getShared('db');
28
29 //This method also requires a input filtering service
30 $filter = $this->_db->get('filter');
31
32 }
33
34 }
35
36 $di = new Phalcon\DI();
37
38 //Register a "db" service in the container
39 $di->set('db', function(){
40 return new Connection(array(
41 "host" => "localhost",
42 "username" => "root",
43 "password" => "secret",
44 "dbname" => "invo"
45 ));
46 });
47
48 //Register a "filter" service in the container
49 $di->set('filter', function(){
50 return new Filter();
51 });
52
53 //Register a "session" service in the container
54 $di->set('session', function(){
55 return new Session();
56 });
57
58 //Pass the service container as unique parameter
59 $some = new SomeComponent($di);
60
61 $some->someTask();
现在,该组件只有访问某种service的时候才需要它,如果它不需要,它甚至不初始化,以节约资源。该组件是高度解耦。他们的行为,或者说他们的任何其他方面都不会影响到组件本身。
我们的实现办法¶
Phalcon\DI 是一个实现了服务的依赖注入功能的组件,它本身也是一个容器。
由于Phalcon高度解耦,Phalcon\DI 是框架用来集成其他组件的必不可少的部分,开发人员也可以使用这个组件依赖注入和管理应用程序中不同类文件的实例。
基本上,这个组件实现了 Inversion of Control 模式。基于此,对象不再以构造函数接收参数或者使用setter的方式来实现注入,而是直接请求服务的依赖注入。这就大大降低了整体程序的复杂性,因为只有一个方法用以获得所需要的一个组件的依赖关系。
此外,这种模式增强了代码的可测试性,从而使它不容易出错。
在容器中注册服务¶
框架本身或开发人员都可以注册服务。当一个组件A要求调用组件B(或它的类的一个实例),可以从容器中请求调用组件B,而不是创建组件B的一个实例。
这种工作方式为我们提供了许多优点:
我们可以更换一个组件,从他们本身或者第三方轻松创建。
在组件发布之前,我们可以充分的控制对象的初始化,并对对象进行各种设置。
我们可以使用统一的方式从组件得到一个结构化的全局实例
服务可以通过以下几种方式注入到容器:
1 <?php
2
3 //Create the Dependency Injector Container
4 $di = new Phalcon\DI();
5
6 //By its class name
7 $di->set("request", 'Phalcon\Http\Request');
8
9 //Using an anonymous function, the instance will lazy loaded
10 $di->set("request", function(){
11 return new Phalcon\Http\Request();
12 });
13
14 //Registering directly an instance
15 $di->set("request", new Phalcon\Http\Request());
16
17 //Using an array definition
18 $di->set("request", array(
19 "className" => 'Phalcon\Http\Request'
20 ));
在上面的例子中,当向框架请求访问一个请求数据时,它将首先确定容器中是否存在这个”reqeust”名称的服务。
容器会反回一个请求数据的实例,开发人员最终得到他们想要的组件。
在上面示例中的每一种方法都有优缺点,具体使用哪一种,由开发过程中的特定场景来决定的。
用一个字符串来设定一个服务非常简单,但缺少灵活性。设置服务时,使用数组则提供了更多的灵活性,而且可以使用较复杂的代码。lambda函数是两者之间一个很好的平衡,但也可能导致更多的维护管理成本。
Phalcon\DI 提供服务的延迟加载。除非开发人员在注入服务的时候直接实例化一个对象,然后存存储到容器中。在容器中,通过数组,字符串等方式存储的服务都将被延迟加载,即只有在请求对象的时候才被初始化。
1 <?php
2
3 //Register a service "db" with a class name and its parameters
4 $di->set("db", array(
5 "className" => "Phalcon\Db\Adapter\Pdo\Mysql",
6 "parameters" => array(
7 "parameter" => array(
8 "host" => "localhost",
9 "username" => "root",
10 "password" => "secret",
11 "dbname" => "blog"
12 )
13 )
14 ));
15
16 //Using an anonymous function
17 $di->set("db", function(){
18 return new Phalcon\Db\Adapter\Pdo\Mysql(array(
19 "host" => "localhost",
20 "username" => "root",
21 "password" => "secret",
22 "dbname" => "blog"
23 ));
24 });
以上这两种服务的注册方式产生相同的结果。然后,通过数组定义的,在后面需要的时候,你可以修改服务参数:
1 <?php
2
3 $di->setParameter("db", 0, array(
4 "host" => "localhost",
5 "username" => "root",
6 "password" => "secret"
7 ));
从容器中获得服务的最简单方式就是使用”get”方法,它将从容器中返回一个新的实例:
1 <?php
2 $request = $di->get("request");
或者通过下面这种魔术方法的形式调用:
1 <?php
2
3 $request = $di->getRequest();
4
5 Phalcon\DI 同时允许服务重用,为了得到一个已经实例化过的服务,可以使用 getShared() 方法的形式来获得服务。
具体的 Phalcon\Http\Request 请求示例:
1 <?php
2
3 $request = $di->getShared("request");
参数还可以在请求的时候通过将一个数组参数传递给构造函数的方式:
1 <?php
2 $component = $di->get("MyComponent", array("some-parameter", "other"))
理解 PHP 依赖注入的更多相关文章
- 理解 PHP 依赖注入 和 控制反转
理解 PHP 依赖注入 和 控制反转 要想理解 PHP 依赖注入 和 控制反转 两个概念,就必须搞清楚如下的两个问题: DI -- Dependency Injection 依赖注入 IoC -- ...
- 【转】理解 PHP 依赖注入 | Laravel IoC容器
Laravel框架的依赖注入确实很强大,并且通过容器实现依赖注入可以有选择性的加载需要的服务,减少初始化框架的开销,下面是我在网上看到的一个帖子,写的很好拿来与大家分享,文章从开始按照传统的类设计数据 ...
- 30行代码让你理解angular依赖注入:angular 依赖注入原理
依赖注入(Dependency Injection,简称DI)是像C#,java等典型的面向对象语言框架设计原则控制反转的一种典型的一种实现方式,angular把它引入到js中,介绍angular依赖 ...
- 理解PHP 依赖注入|Laravel IoC容器
看Laravel的IoC容器文档只是介绍实例,但是没有说原理,之前用MVC框架都没有在意这个概念,无意中在phalcon的文档中看到这个详细的介绍,感觉豁然开朗,复制粘贴过来,主要是好久没有写东西了, ...
- 通过laravel理解IoC(控制反转)容器和DI(依赖注入)
原文地址: http://www.insp.top/learn-laravel-container ,转载务必保留来源,谢谢了! 容器,字面上理解就是装东西的东西.常见的变量.对象属性等都可以算是容器 ...
- Laravel 服务容器实例教程 —— 深入理解控制反转(IoC)和依赖注入(DI)
容器,字面上理解就是装东西的东西.常见的变量.对象属性等都可以算是容器.一个容器能够装什么,全部取决于你对该容器的定义.当然,有这样一种容器,它存放的不是文本.数值,而是对象.对象的描述(类.接口)或 ...
- 深入浅出理解依赖注入这种由外部负责其依赖需求的行为,我们可以称其为 “控制反转(IoC)”
原文地址: http://www.insp.top/learn-laravel-container ,转载务必保留来源,谢谢了! 这个组件现在可以很简单的获取到它所需要的服务,服务采用延迟加载的方式, ...
- 理解依赖注入 for Zend framework 2
依赖注入(Dependency Injection),也成为控制反转(Inversion of Control),一种设计模式,其目的是解除类之间的依赖关系. 假设我们需要举办一个Party,Part ...
- 【NetCore】依赖注入的一些理解与分享
依赖注入 DI 前言 声明:我是一个菜鸟,此文是自己的理解,可能正确,可能有误.仅供学习参考帮助理解,如果直接拷贝代码使用造成损失概不负责. 相关的文章很多,我就仅在代码层面描述我所理解的依赖注入是个 ...
随机推荐
- 不重叠的线段 51nod
链接 [http://www.51nod.com/onlineJudge/questionCode.html#problemId=1133¬iceId=468024] 题意 X轴上有N条 ...
- 个人作业Week2-代码复审(修改明确了要求)
代码复审 零,说在前面的话 大家完成了个人项目之后,都写了很多代码. 这些代码可能: 大括号换行/不换行 使用tab缩进/使用空格缩进 变量名函数名的定义很好/不好 每个函数都有详细的注释解释函数的功 ...
- 《Linux内核设计与实现》第四章学习笔记
<Linux内核设计与实现>第四章学习笔记 ——进程调度 姓名:王玮怡 学号:20135116 一.多任务 1.多任务操作系统的含义 多任务操作系统就是能同时并发地交 ...
- Java计算器(结对)
一:题目简介 我们要做的是一个多功能计算器,Java程序编辑器是:图形界面.线程.流与文件等技术的综合应用. 图形界面的实现:考虑到简单.实用.高效等特点,就选择了Swing来完成实现,在选择组件上, ...
- EnglishGame
https://github.com/zhangxue520/EnglishGame/blob/master/EnglishGame <程序设计实践I> 题目: 打字训练测试软 ...
- SpringBoot-简单实例
在进行实例之前,首先须确保电脑环境变量已经配置好,包括jdk.maven.此文章不做描述,不清楚自行百度. 第一步:来到springboot官网(https://start.spring.io/)下载 ...
- 数据结构--图 的JAVA实现(下)
在上一篇文章中记录了如何实现图的邻接表.本文借助上一篇文章实现的邻接表来表示一个有向无环图. 1,概述 图的实现与邻接表的实现最大的不同就是,图的实现需要定义一个数据结构来存储所有的顶点以及能够对图进 ...
- 重载(overload)、覆盖(override)、隐藏(hide)的区别
http://blog.csdn.net/yanjun_1982/archive/2005/09/02/470405.aspx 重载是指不同的函数使用相同的函数名,但是函数的参数个数或类型不同.调用的 ...
- NopCommerce源码架构
我们承接以下nop相关的业务,欢迎联系我们. 我们承接NopCommerce定制个性化开发: Nopcommerce二次开发 Nopcommerce主题开发 基于Nopcommerce的二次开发的电子 ...
- Neo4j学习案例【转】
转自 打怪的蚂蚁 CSDN: https://blog.csdn.net/xgjianstart/article/details/77285334 neo4j有社区版本和企业版.社区版本是免费的,只支 ...