在看laravel文档的时候,有一个服务容器(IoC)的概念。它是这样介绍的:Laravel 服务容器是一个用于管理类依赖和执行依赖注入的强大工具。依赖注入听上去很花哨,其实质是通过构造函数或者某些情况下通过「setter」方法将类依赖注入到类中。

但是上面并没有说明原理,是怎么来的呢?

在搜索的时候,看到了Phalcon的中文文档(http://docs.iphalcon.cn/),这份文档写的非常好,其中里面就有一篇《Dependency Injection Explained》,详细解释了依赖注入的原理。

外国人写的文档还是很详细易懂的。

下面的文章来自:http://docs.iphalcon.cn/reference/di-explained.html

接下来的例子有些长,但解释了为什么我们使用依赖注入与服务定位器. 首先,假设我们正在开发一个组件,叫SomeComponent,它执行的内容现在还不重要。 我们的组件需要依赖数据库的连接。

在下面第一个例子中,数据库的连接是在组件内部建立的。这种方法是不实用的;事实上这样做的话,我们不能改变创建数据库连接的参数或者选择不同的数据库系统,因为连接是当组件被创建时建立的。

<?php

class SomeComponent
{
/**
* 连接数据库的实例是被写死在组件的内部
* 因此,我们很难从外部替换或者改变它的行为
*/
public function someDbTask()
{
$connection = new Connection(
[
"host" => "localhost",
"username" => "root",
"password" => "secret",
"dbname" => "invo",
]
); // ...
}
} $some = new SomeComponent(); $some->someDbTask();

为了解决这样的情况,我们建立一个setter,在使用前注入独立外部依赖。现在,看起来似乎是一个不错的解决办法:

<?php

class SomeComponent
{
protected $_connection; /**
* 设置外部传入的数据库的连接实例
*/
public function setConnection($connection)
{
$this->_connection = $connection;
} public function someDbTask()
{
$connection = $this->_connection; // ...
}
} $some = new SomeComponent(); // 建立数据库连接实例
$connection = new Connection(
[
"host" => "localhost",
"username" => "root",
"password" => "secret",
"dbname" => "invo",
]
); // 向组件注入数据连接实例
$some->setConnection($connection); $some->someDbTask();

想一下,假设我们使用这个组件在应用内的好几个地方都用到,然而我们在注入连接实例时还需要建立好几次数据的连接实例。 如果我们可以获取到数据库的连接实例而不用每次都要创建新的连接实例,使用某种全局注册表可以解决这样的问题:

<?php

class Registry
{
/**
* 返回数据库连接实例
*/
public static function getConnection()
{
return new Connection(
[
"host" => "localhost",
"username" => "root",
"password" => "secret",
"dbname" => "invo",
]
);
}
} class SomeComponent
{
protected $_connection; /**
* 设置外部传入的数据库的连接实例
*/
public function setConnection($connection)
{
$this->_connection = $connection;
} public function someDbTask()
{
$connection = $this->_connection; // ...
}
} $some = new SomeComponent(); // 把注册表中的连接实例传递给组件
$some->setConnection(Registry::getConnection()); $some->someDbTask();

现在,让我们设想一下,我们必须实现2个方法,第一个方法是总是创建一个新的连接,第二方法是总是使用一个共享连接:

<?php

class Registry
{
protected static $_connection; /**
* 建立一个新的连接实例
*/
protected static function _createConnection()
{
return new Connection(
[
"host" => "localhost",
"username" => "root",
"password" => "secret",
"dbname" => "invo",
]
);
} /**
* 只建立一个连接实例,后面的请求只返回该连接实例
*/
public static function getSharedConnection()
{
if (self::$_connection === null) {
self::$_connection = self::_createConnection();
} return self::$_connection;
} /**
* 总是返回一个新的连接实例
*/
public static function getNewConnection()
{
return self::_createConnection();
}
} class SomeComponent
{
protected $_connection; /**
* 设置外部传入的数据库的连接实例
*/
public function setConnection($connection)
{
$this->_connection = $connection;
} /**
* 这个方法总是需要共享连接实例
*/
public function someDbTask()
{
$connection = $this->_connection; // ...
} /**
* 这个方法总是需要新的连接实例
*/
public function someOtherDbTask($connection)
{ }
} $some = new SomeComponent(); // 注入共享连接实例
$some->setConnection(
Registry::getSharedConnection()
); $some->someDbTask(); // 这里我们总是传递一个新的连接实例
$some->someOtherDbTask(
Registry::getNewConnection()
);

到目前为止,我们已经看到依赖注入怎么解决我们的问题了。把依赖作为参数来传递,而不是建立在内部建立它们,这使我们的应用更加容易维护和更加解耦。不管怎么样,长期来说,这种形式的依赖注入有一些缺点。

例如,如果这个组件有很多依赖, 我们需要创建多个参数的setter方法​​来传递依赖关系,或者建立一个多个参数的构造函数来传递它们,另外在使用组件前还要每次都创建依赖,这让我们的代码像这样不易维护:

<?php

// 创建依赖实例或从注册表中查找
$connection = new Connection();
$session = new Session();
$fileSystem = new FileSystem();
$filter = new Filter();
$selector = new Selector(); // 把实例作为参数传递给构造函数
$some = new SomeComponent($connection, $session, $fileSystem, $filter, $selector); // ... 或者使用setter
$some->setConnection($connection);
$some->setSession($session);
$some->setFileSystem($fileSystem);
$some->setFilter($filter);
$some->setSelector($selector);

假设我们必须在应用的不同地方使用和创建这些对象。如果当你永远不需要任何依赖实例时,你需要去删掉构造函数的参数,或者去删掉注入的setter。为了解决这样的问题,我们再次回到全局注册表创建组件。不管怎么样,在创建对象之前,它增加了一个新的抽象层:

<?php

class SomeComponent
{
// ... /**
* Define a factory method to create SomeComponent instances injecting its dependencies
*/
public static function factory()
{
$connection = new Connection();
$session = new Session();
$fileSystem = new FileSystem();
$filter = new Filter();
$selector = new Selector(); return new self($connection, $session, $fileSystem, $filter, $selector);
}
}

瞬间,我们又回到刚刚开始的问题了,我们再次创建依赖实例在组件内部!我们可以继续前进,找出一个每次能奏效的方法去解决这个问题。但似乎一次又一次,我们又回到了不实用的例子中。

一个实用和优雅的解决方法,是为依赖实例提供一个容器。这个容器担任全局的注册表,就像我们刚才看到的那样。使用依赖实例的容器作为一个桥梁来获取依赖实例,使我们能够降低我们的组件的复杂性:

<?php

use Phalcon\Di;
use Phalcon\DiInterface; class SomeComponent
{
protected $_di; public function __construct(DiInterface $di)
{
$this->_di = $di;
} public function someDbTask()
{
// 获得数据库连接实例
// 总是返回一个新的连接
$connection = $this->_di->get("db");
} public function someOtherDbTask()
{
// 获得共享连接实例
// 每次请求都返回相同的连接实例
$connection = $this->_di->getShared("db"); // 这个方法也需要一个输入过滤的依赖服务
$filter = $this->_di->get("filter");
}
} $di = new Di(); // 在容器中注册一个db服务
$di->set(
"db",
function () {
return new Connection(
[
"host" => "localhost",
"username" => "root",
"password" => "secret",
"dbname" => "invo",
]
);
}
); // 在容器中注册一个filter服务
$di->set(
"filter",
function () {
return new Filter();
}
); // 在容器中注册一个session服务
$di->set(
"session",
function () {
return new Session();
}
); // 把传递服务的容器作为唯一参数传递给组件
$some = new SomeComponent($di); $some->someDbTask();

这个组件现在可以很简单的获取到它所需要的服务,服务采用延迟加载的方式,只有在需要使用的时候才初始化,这也节省了服务器资源。这个组件现在是高度解耦。例如,我们可以替换掉创建连接的方式,它们的行为或它们的任何其他方面,也不会影响该组件。

依赖注入与服务定位器(Dependency Injection/Service Location)

使用Phacon\Di可以整合框架的不同组件。

开发者也可以使用这个组件去注入依赖和管理的应用程序中来自不同类的全局实例。

基本上,这个组件实现了 [控制反转](http://zh.wikipedia.org/wiki/%E6%8E%A7%E5%88%B6%E5%8F%8D%E8%BD%AC) 的模式。使用这种模式,组件的对象不用再使用setter或者构造函数去接受依赖实例,而是使用请求服务的依赖注入。这减少了总的复杂性,因为在组件内,只有一个方法去获取所需的依赖实例

字符串注册

使用字符串注册服务需要一个有效的类名称,它将返回指定的类对象,如果类还没有加载的话,将使用自动加载器实例化对象。这种类型不允许向构造函数指定参数:

<?php

// 返回 new Phalcon\Http\Request(); 对象
$di->set(
"request",
"Phalcon\\Http\\Request"
);

类实例(CLASS INSTANCES)注册

这种类型注册服务需要一个对象。实际上,这个服务不再需要初始化,因为它已经是一个对象,可以说,这不是一个真正的依赖注入,但是如果你想强制总是返回相同的对象/值,使用这种方式还是有用的

<?php

use Phalcon\Http\Request;

// 返回 Phalcon\Http\Request(); 对象
$di->set(
"request",
new Request()
);

闭包与匿名函数(CLOSURES/ANONYMOUS FUNCTIONS)注册

这个方法提供了更加自由的方式去注册依赖,但是如果你想从外部改变实例化的参数而不用改变注册服务的代码,这是很困难的:

<?php

use Phalcon\Db\Adapter\Pdo\Mysql as PdoMysql;

$di->set(
"db",
function () {
return new PdoMysql(
[
"host" => "localhost",
"username" => "root",
"password" => "secret",
"dbname" => "blog",
]
);
}
);

这些限制是可以克服的,通过传递额外的变量到闭包函数里面:

<?php

use Phalcon\Config;
use Phalcon\Db\Adapter\Pdo\Mysql as PdoMysql; $config = new Config(
[
"host" => "127.0.0.1",
"username" => "user",
"password" => "pass",
"dbname" => "my_database",
]
); // 把当前域的$config变量传递给匿名函数使用
$di->set(
"db",
function () use ($config) {
return new PdoMysql(
[
"host" => $config->host,
"username" => $config->username,
"password" => $config->password,
"dbname" => $config->name,
]
);
}
);

You can also access other DI services using the get() method:

<?php

use Phalcon\Config;
use Phalcon\Db\Adapter\Pdo\Mysql as PdoMysql; $di->set(
"config",
function () {
return new Config(
[
"host" => "127.0.0.1",
"username" => "user",
"password" => "pass",
"dbname" => "my_database",
]
);
}
); // Using the 'config' service from the DI
$di->set(
"db",
function () {
$config = $this->get("config"); return new PdoMysql(
[
"host" => $config->host,
"username" => $config->username,
"password" => $config->password,
"dbname" => $config->name,
]
);
}
);

复杂的注册(Complex Registration)

如果要求不用实例化/解析服务,就可以改变定义服务的话,我们需要使用数组的方式去定义服务。使用数组去定义服务可以更加详细:

?php

use Phalcon\Logger\Adapter\File as LoggerFile;

// 通过类名和参数,注册logger服务
$di->set(
"logger",
[
"className" => "Phalcon\\Logger\\Adapter\\File",
"arguments" => [
[
"type" => "parameter",
"value" => "../apps/logs/error.log",
]
]
]
); // 使用匿名函数的方式
$di->set(
"logger",
function () {
return new LoggerFile("../apps/logs/error.log");
}
);

上面两种注册服务的方式的结果是一样的。然而,使用数组定义的话,在需要的时候可以变更注册服务的参数:

<?php

// 改变logger服务的类名
$di->getService("logger")->setClassName("MyCustomLogger"); // 不用实例化就可以改变第一个参数值
$di->getService("logger")->setParameter(
0,
[
"type" => "parameter",
"value" => "../apps/logs/error.log",
]
);

除了使用数组的语法注册服务,你还可以使用以下三种类型的依赖注入:

构造函数注入(CONSTRUCTOR INJECTION)

这个注入方式是通过传递依赖/参数到类的构造函数。让我们假设我们有下面的组件:

<?php

namespace SomeApp;

use Phalcon\Http\Response;

class SomeComponent
{
/**
* @var Response
*/
protected $_response; protected $_someFlag; public function __construct(Response $response, $someFlag)
{
$this->_response = $response;
$this->_someFlag = $someFlag;
}
}

这个服务可以这样被注入:

<?php

$di->set(
"response",
[
"className" => "Phalcon\\Http\\Response"
]
); $di->set(
"someComponent",
[
"className" => "SomeApp\\SomeComponent",
"arguments" => [
[
"type" => "service",
"name" => "response",
],
[
"type" => "parameter",
"value" => true,
],
]
]
);

reponse服务(Phalcon\Http\Response)作为第一个参数传递给构造函数,与此同时,一个布尔类型的值(true)作为第二个参数传递。

设值注入(SETTER INJECTION)

类中可能有setter去注入可选的依赖,前面那个class可以修改成通过setter来注入依赖的方式:

?php

namespace SomeApp;

use Phalcon\Http\Response;

class SomeComponent
{
/**
* @var Response
*/
protected $_response; protected $_someFlag; public function setResponse(Response $response)
{
$this->_response = $response;
} public function setFlag($someFlag)
{
$this->_someFlag = $someFlag;
}
}

用setter方式来注入的服务可以通过下面的方式来注册:

<?php

$di->set(
"response",
[
"className" => "Phalcon\\Http\\Response",
]
); $di->set(
"someComponent",
[
"className" => "SomeApp\\SomeComponent",
"calls" => [
[
"method" => "setResponse",
"arguments" => [
[
"type" => "service",
"name" => "response",
]
]
],
[
"method" => "setFlag",
"arguments" => [
[
"type" => "parameter",
"value" => true,
]
]
]
]
]
);

属性注入(PROPERTIES INJECTION)

这是一个不太常用的方式,这种方式的注入是通过类的public属性来注入:

<?php

namespace SomeApp;

use Phalcon\Http\Response;

class SomeComponent
{
/**
* @var Response
*/
public $response; public $someFlag;
}

通过属性注入的服务,可以像下面这样注册:

<?php

$di->set(
"response",
[
"className" => "Phalcon\\Http\\Response",
]
); $di->set(
"someComponent",
[
"className" => "SomeApp\\SomeComponent",
"properties" => [
[
"name" => "response",
"value" => [
"type" => "service",
"name" => "response",
],
],
[
"name" => "someFlag",
"value" => [
"type" => "parameter",
"value" => true,
],
]
]
]
);

Array Syntax 数组注册

使用数组的方式去注册服务也是可以的:

?php

use Phalcon\Di;
use Phalcon\Http\Request; // 创建一个依赖注入容器
$di = new Di(); // 通过类名称设置服务
$di["request"] = "Phalcon\\Http\\Request"; // 使用匿名函数去设置服务,这个实例将被延迟加载
$di["request"] = function () {
return new Request();
}; // 直接注册一个实例
$di["request"] = new Request(); // 使用数组方式定义服务
$di["request"] = [
"className" => "Phalcon\\Http\\Request",
];

在上面的例子中,当框架需要访问request服务的内容,它会在容器里面查找名为‘request’的服务。 在容器中将返回所需要的服务的实例。当有需要时,开发者可能最终需要替换这个组件。

每个方法(在上面的例子证明)用于设置/注册服务方面具都具有优势和劣势。这是由开发者和特别的要求决定具体使用哪个。

通过字符串设置一个服务是很简单,但是缺乏灵活性。通过数组设置服务提供了更加灵活的方式,但是使代码更复杂。匿名函数是上述两者之间的一个很好的平衡,但是会导致比预期的更多维护。

Phalcon\Di 对每个储存的服务提供了延迟加载。除非开发者选择直接实例化一个对象并将其存储在容器中,任何储存在里面的对象(通过数组,字符串等等设置的)都将延迟加载,即只要当使用到时才实例化。

从容器中获取服务

从容器中获取一个服务是一件简单的事情,只要通过“get”方法就可以。这将返回一个服务的新实例:

<?php $request = $di->get("request");

或者通过魔术方法的方式获取:


<?php

$request = $di->getRequest();

或者通过访问数组的方式获取:

<?php

$request = $di["request"];

参数可以传递到构造函数中,通过添加一个数组的参数到get方法中:

<?php

// 将返回:new MyComponent("some-parameter", "other")
$component = $di->get(
"MyComponent",
[
"some-parameter",
"other",
]
);

理解依赖注入,laravel IoC容器的更多相关文章

  1. 理解PHP 依赖注入|Laravel IoC容器

    看Laravel的IoC容器文档只是介绍实例,但是没有说原理,之前用MVC框架都没有在意这个概念,无意中在phalcon的文档中看到这个详细的介绍,感觉豁然开朗,复制粘贴过来,主要是好久没有写东西了, ...

  2. 【转】理解 PHP 依赖注入 | Laravel IoC容器

    Laravel框架的依赖注入确实很强大,并且通过容器实现依赖注入可以有选择性的加载需要的服务,减少初始化框架的开销,下面是我在网上看到的一个帖子,写的很好拿来与大家分享,文章从开始按照传统的类设计数据 ...

  3. C#中的依赖注入和IoC容器

    在本文中,我们将通过用C#重构一个非常简单的代码示例来解释依赖注入和IoC容器. 简介: 依赖注入和IoC乍一看可能相当复杂,但它们非常容易学习和理解. 在本文中,我们将通过在C#中重构一个非常简单的 ...

  4. 【转】理解依赖注入(IOC)和学习Unity

    IOC:英文全称:Inversion of Control,中文名称:控制反转,它还有个名字叫依赖注入(Dependency Injection).作用:将各层的对象以松耦合的方式组织在一起,解耦,各 ...

  5. 理解依赖注入(IOC)和学习Unity

    资料1: IOC:英文全称:Inversion of Control,中文名称:控制反转,它还有个名字叫依赖注入(Dependency Injection). 作用:将各层的对象以松耦合的方式组织在一 ...

  6. 学习Unity -- 理解依赖注入(IOC)三种方式依赖注入

    IOC:英文全称:Inversion of Control,中文名称:控制反转,它还有个名字叫依赖注入(Dependency Injection).作用:将各层的对象以松耦合的方式组织在一起,解耦,各 ...

  7. SpringIOC的概念理解、构造器注入、setter注入、p命名空间注入、IOC容器介绍与比较

    1.IOC概念理解 IOC(Inversion of Control)即“控制反转”,不是什么技术,而是一种设计思想.在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象 ...

  8. 看过这些我明白了依赖注入及IoC

    背景 最近一段时间在学习laravel框架,了解到这个框架一个比较核心的概念就是服务容器,而服务容器似乎又和依赖注入有关系.但是碍于官方关于这方面的讲解篇幅过少,所以自学了一下. 自学的途径也跟大家一 ...

  9. 深度理解依赖注入(Dependence Injection)

    前面的话:提到依赖注入,大家都会想到老马那篇经典的文章.其实,本文就是相当于对那篇文章的解读.所以,如果您对原文已经有了非常深刻的理解,完全不需要再看此文:但是,如果您和笔者一样,以前曾经看过,似乎看 ...

  10. [转]深度理解依赖注入(Dependence Injection)

    http://www.cnblogs.com/xingyukun/archive/2007/10/20/931331.html 前面的话:提到依赖注入,大家都会想到老马那篇经典的文章.其实,本文就是相 ...

随机推荐

  1. Eclipse导出包含第三方Jar的工程

    基于第三方开源的Jar包封装了一个工具类,在导出成Jar包后,引用新生成的Jar包,却报找不到类的错误.看了一下生成的Jar包,发现根本没有包含第三方Jar包的相关class.这是导出第三方Jar包时 ...

  2. 微信小程序(基本知识点)

    创建页面的两种方式:   1.通过创建文件夹的方式创建(.wxml/.wxss/.json/.js/)   2.在app.json的"pages": []中添加路径"pa ...

  3. (办公)mybatis工作中常见的问题(不定时更新)

    1.mybatis的like查询的方式. <if test="shopName != null and shopName != ''"> <bind name=& ...

  4. 测者的测试技术手册:Java中的null类型是测试不可超越的鸿沟

    null是一个非常非常特殊的类型,对于每一个测试人员都要十分小心null的存在的可能性.同时null也让很多RD头疼,甚至连Java的设计者都成人null是一个设计失误.这篇文章,测者想聊聊这个让很多 ...

  5. python datetime操作

    #datetime object转化为timestamp import datetime now = datetime.datetime.now() now_timestamp = time.mkti ...

  6. jquery 同步加载

    jquery在前端展示时,如果需要从服务器获取信息然后在更新,需要设置同步加载. async属性设置为:false即可. $.ajax({ url : 'test.php', type : 'post ...

  7. hbase 预分区与自动分区

    我们知道,HBASE在创建表的时候,会自动为表分配一个Region,当一个Region过大达到默认的阈值时(默认10GB大小),HBase中该Region将会进行split,分裂为2个Region,以 ...

  8. 创建一个Windows服务程序与实现定时器效果

    1.创建一个Windows服务程序 一.   新建Window服务项目 二.   添加安装程序 三.   配置服务属性 四.   编写定时器代码 publicpartialclassService1 ...

  9. Jquery消息提示插件toastr使用详解

    toastr是一个基于jQuery简单.漂亮的消息提示插件,使用简单.方便,可以根据设置的超时时间自动消失. 1.使用很简单,首选引入toastr的js.css文件 html <script s ...

  10. keil 中报错和警告提示解决办法

    1.warning: #1-D: last line of file ends without a newline 解决办法:在文件最后一行加入一个回车. 2.error: #134: expecte ...