在看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. animate-queue和step-animate

    Step-animate: 分为3部分:{配置},{step:function(){...},duration:1000} <div id="warpper" style=& ...

  2. vue学习之ajax 简单快速使用axios

    vue2.x 推荐使用axios 1.首先使用安装 npm install axios -S 2.在哪用在哪引入 import axios from 'axios' 或则在main.js 中引入 在申 ...

  3. Windows下建立ArcGIS Server集群

    原创文章,转载须标明出处自: http://www.cnblogs.com/gisspace/p/8269525.html -------------------------------------- ...

  4. AIDL使用以及原理分析

    AIDL使用以及IPC原理分析(进程间通信) 概要 为了大家能够更好的理解android的进程间通信原理,以下将会从以下几个方面讲解跨进程通讯信: 1. 必要了解的概念 2. 为什么要使用aidl进程 ...

  5. ORA-02266错误的批量生成脚本解决方案

    ORA-02266: unique/primary keys in table referenced by enabled foreign keys这篇博客是很早之前总结的一篇文章,最近导数时使用TR ...

  6. 将对象xml序列化和反序列化

    //将一个对象按XML序列化的方式写入到一个文件,使用的默认的UTF8编码格式 //o为要序列化的对象 //path保存文件的路径 public static object  _lockObj=new ...

  7. 【原】使用IDEA创建Maven工程时提示"...xxx/pom.xml already exists in VFS"的解决

    问题:使用IDEA创建Maven工程时提示"...xxx/pom.xml already exists in VFS",怎么办? 解决:如果只是删除工程,还会有这样的提示.说到底, ...

  8. Linux - 微软无线鼠标滚动过快问题

    Linux - 微软无线鼠标滚动过快问题 使用了一段时间的 Manjaro , 感觉相当不错, 但有一个蛋疼的地方就是每次滚动鼠标滚轮, 都会切换一页以上的页面, 总是有一部分看不到. 之前以为是 L ...

  9. WPF软件开发系统之二——水环境检测Surface触摸屏软件开发

    该系统采用C#.WPF语言开发,开发工具Visual Studio 2015.Blend,环境WIN7系统及以上,适用于PC.Windows触摸屏Surface等设备. 部分截图效果如下: 开发工具环 ...

  10. opendir函数和readdir函数内涵及用法

    工作中遇到奇怪的事,加载增量的时候加载不上.于是开始分析原因,log里边没有任何错误信息,只有加载完成的标志.增量的数据在目录里边是存在的,但是显示的目录大小却不是4096,而是17,不知道为什么.后 ...