php 依赖注入容器
原文: http://blog.csdn.net/realghost/article/details/35212285
https://my.oschina.net/cxz001/blog/533166
http://www.oschina.net/code/snippet_1171845_48046
http://blog.csdn.net/wzllai/article/details/24485245
看Laravel的IoC容器文档只是介绍实例,但是没有说原理,之前用MVC框架都没有在意这个概念,无意中在phalcon的文档中看到这个详细的介绍,感觉豁然开朗,复制粘贴过来,主要是好久没有写东西了,现在确实很懒变得!
首先,我们假设,我们要开发一个组件命名为SomeComponent。这个组件中现在将要注入一个数据库连接。
在这个例子中,数据库连接在component中被创建,这种方法是不切实际的,这样做的话,我们将不能改变数据库连接参数及数据库类型等一些参数。
- <?php
- class SomeComponent
- {
- /**
- * The instantiation of the connection is hardcoded inside
- * the component so is difficult to replace it externally
- * or change its behavior
- */
- public function someDbTask()
- {
- $connection = new Connection(array(
- "host" => "localhost",
- "username" => "root",
- "password" => "secret",
- "dbname" => "invo"
- ));
- // ...
- }
- }
- $some = new SomeComponent();
- $some->someDbTask();
为了解决上面所说的问题,我们需要在使用前创建一个外部连接,并注入到容器中。就目前而言,这看起来是一个很好的解决方案:
- <?php
- class SomeComponent
- {
- protected $_connection;
- /**
- * Sets the connection externally
- */
- public function setConnection($connection)
- {
- $this->_connection = $connection;
- }
- public function someDbTask()
- {
- $connection = $this->_connection;
- // ...
- }
- }
- $some = new SomeComponent();
- //Create the connection
- $connection = new Connection(array(
- "host" => "localhost",
- "username" => "root",
- "password" => "secret",
- "dbname" => "invo"
- ));
- //Inject the connection in the component
- $some->setConnection($connection);
- $some->someDbTask();
现在我们来考虑一个问题,我们在应用程序中的不同地方使用此组件,将多次创建数据库连接。使用一种类似全局注册表的方式,从这获得一个数据库连接实例,而不是使用一次就创建一次。
- <?php
- class Registry
- {
- /**
- * Returns the connection
- */
- public static function getConnection()
- {
- return new Connection(array(
- "host" => "localhost",
- "username" => "root",
- "password" => "secret",
- "dbname" => "invo"
- ));
- }
- }
- class SomeComponent
- {
- protected $_connection;
- /**
- * Sets the connection externally
- */
- public function setConnection($connection){
- $this->_connection = $connection;
- }
- public function someDbTask()
- {
- $connection = $this->_connection;
- // ...
- }
- }
- $some = new SomeComponent();
- //Pass the connection defined in the registry
- $some->setConnection(Registry::getConnection());
- $some->someDbTask();
现在,让我们来想像一下,我们必须在组件中实现两个方法,首先需要创建一个新的数据库连接,第二个总是获得一个共享连接:
- <?php
- class Registry
- {
- protected static $_connection;
- /**
- * Creates a connection
- */
- protected static function _createConnection()
- {
- return new Connection(array(
- "host" => "localhost",
- "username" => "root",
- "password" => "secret",
- "dbname" => "invo"
- ));
- }
- /**
- * Creates a connection only once and returns it
- */
- public static function getSharedConnection()
- {
- if (self::$_connection===null){
- $connection = self::_createConnection();
- self::$_connection = $connection;
- }
- return self::$_connection;
- }
- /**
- * Always returns a new connection
- */
- public static function getNewConnection()
- {
- return self::_createConnection();
- }
- }
- class SomeComponent
- {
- protected $_connection;
- /**
- * Sets the connection externally
- */
- public function setConnection($connection){
- $this->_connection = $connection;
- }
- /**
- * This method always needs the shared connection
- */
- public function someDbTask()
- {
- $connection = $this->_connection;
- // ...
- }
- /**
- * This method always needs a new connection
- */
- public function someOtherDbTask($connection)
- {
- }
- }
- $some = new SomeComponent();
- //This injects the shared connection
- $some->setConnection(Registry::getSharedConnection());
- $some->someDbTask();
- //Here, we always pass a new connection as parameter
- $some->someOtherDbTask(Registry::getConnection());
到此为止,我们已经看到了如何使用依赖注入解决我们的问题。不是在代码内部创建依赖关系,而是让其作为一个参数传递,这使得我们的程序更容易维护,降低程序代码的耦合度,实现一种松耦合。但是从长远来看,这种形式的依赖注入也有一些缺点。
例如,如果组件中有较多的依赖关系,我们需要创建多个setter方法传递,或创建构造函数进行传递。另外,每次使用组件时,都需要创建依赖组件,使代码维护不太易,我们编写的代码可能像这样:
- <?php
- //Create the dependencies or retrieve them from the registry
- $connection = new Connection();
- $session = new Session();
- $fileSystem = new FileSystem();
- $filter = new Filter();
- $selector = new Selector();
- //Pass them as constructor parameters
- $some = new SomeComponent($connection, $session, $fileSystem, $filter, $selector);
- // ... or using setters
- $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
- class SomeComponent
- {
- protected $_di;
- public function __construct($di)
- {
- $this->_di = $di;
- }
- public function someDbTask()
- {
- // Get the connection service
- // Always returns a new connection
- $connection = $this->_di->get('db');
- }
- public function someOtherDbTask()
- {
- // Get a shared connection service,
- // this will return the same connection everytime
- $connection = $this->_di->getShared('db');
- //This method also requires a input filtering service
- $filter = $this->_db->get('filter');
- }
- }
- $di = new Phalcon\DI();
- //Register a "db" service in the container
- $di->set('db', function(){
- return new Connection(array(
- "host" => "localhost",
- "username" => "root",
- "password" => "secret",
- "dbname" => "invo"
- ));
- });
- //Register a "filter" service in the container
- $di->set('filter', function(){
- return new Filter();
- });
- //Register a "session" service in the container
- $di->set('session', function(){
- return new Session();
- });
- //Pass the service container as unique parameter
- $some = new SomeComponent($di);
- $some->someTask();
现在,该组件只有访问某种service的时候才需要它,如果它不需要,它甚至不初始化,以节约资源。该组件是高度解耦。他们的行为,或者说他们的任何其他方面都不会影响到组件本身。
我们的实现办法
Phalcon\DI 是一个实现了服务的依赖注入功能的组件,它本身也是一个容器。
由于Phalcon高度解耦,Phalcon\DI 是框架用来集成其他组件的必不可少的部分,开发人员也可以使用这个组件依赖注入和管理应用程序中不同类文件的实例。
基本上,这个组件实现了 Inversion of Control 模式。基于此,对象不再以构造函数接收参数或者使用setter的方式来实现注入,而是直接请求服务的依赖注入。这就大大降低了整体程序的复杂性,因为只有一个方法用以获得所需要的一个组件的依赖关系。
此外,这种模式增强了代码的可测试性,从而使它不容易出错。
在容器中注册服务¶
框架本身或开发人员都可以注册服务。当一个组件A要求调用组件B(或它的类的一个实例),可以从容器中请求调用组件B,而不是创建组件B的一个实例。
这种工作方式为我们提供了许多优点:
我们可以更换一个组件,从他们本身或者第三方轻松创建。
在组件发布之前,我们可以充分的控制对象的初始化,并对对象进行各种设置。
我们可以使用统一的方式从组件得到一个结构化的全局实例
服务可以通过以下几种方式注入到容器:
- <?php
- //Create the Dependency Injector Container
- $di = new Phalcon\DI();
- //By its class name
- $di->set("request", 'Phalcon\Http\Request');
- //Using an anonymous function, the instance will lazy loaded
- $di->set("request", function(){
- return new Phalcon\Http\Request();
- });
- //Registering directly an instance
- $di->set("request", new Phalcon\Http\Request());
- //Using an array definition
- $di->set("request", array(
- "className" => 'Phalcon\Http\Request'
- ));
在上面的例子中,当向框架请求访问一个请求数据时,它将首先确定容器中是否存在这个”reqeust”名称的服务。
容器会反回一个请求数据的实例,开发人员最终得到他们想要的组件。
在上面示例中的每一种方法都有优缺点,具体使用哪一种,由开发过程中的特定场景来决定的。
用一个字符串来设定一个服务非常简单,但缺少灵活性。设置服务时,使用数组则提供了更多的灵活性,而且可以使用较复杂的代码。lambda函数是两者之间一个很好的平衡,但也可能导致更多的维护管理成本。
Phalcon\DI 提供服务的延迟加载。除非开发人员在注入服务的时候直接实例化一个对象,然后存存储到容器中。在容器中,通过数组,字符串等方式存储的服务都将被延迟加载,即只有在请求对象的时候才被初始化。
- <?php
- //Register a service "db" with a class name and its parameters
- $di->set("db", array(
- "className" => "Phalcon\Db\Adapter\Pdo\Mysql",
- "parameters" => array(
- "parameter" => array(
- "host" => "localhost",
- "username" => "root",
- "password" => "secret",
- "dbname" => "blog"
- )
- )
- ));
- //Using an anonymous function
- $di->set("db", function(){
- return new Phalcon\Db\Adapter\Pdo\Mysql(array(
- "host" => "localhost",
- "username" => "root",
- "password" => "secret",
- "dbname" => "blog"
- ));
- });
以上这两种服务的注册方式产生相同的结果。然后,通过数组定义的,在后面需要的时候,你可以修改服务参数:
- <?php
- $di->setParameter("db", 0, array(
- "host" => "localhost",
- "username" => "root",
- "password" => "secret"
- ));
从容器中获得服务的最简单方式就是使用”get”方法,它将从容器中返回一个新的实例:
- <?php
- $request = $di->get("request");
或者通过下面这种魔术方法的形式调用:
- <?php
- $request = $di->getRequest();
Phalcon\DI 同时允许服务重用,为了得到一个已经实例化过的服务,可以使用 getShared() 方法的形式来获得服务。
具体的 Phalcon\Http\Request 请求示例:
- <?php
- $request = $di->getShared("request");
参数还可以在请求的时候通过将一个数组参数传递给构造函数的方式:
- <?php
- $component = $di->get("MyComponent", array("some-parameter", "other"))
php 依赖注入容器的更多相关文章
- WPF PRISM开发入门二(Unity依赖注入容器使用)
这篇博客将通过一个控制台程序简单了解下PRISM下Unity依赖注入容器的使用.我已经创建了一个例子,通过一个控制台程序进行加减乘除运算,项目当中将输入输出等都用接口封装后,结构如下: 当前代码可以点 ...
- Yii2.0 依赖注入(DI)和依赖注入容器的原理
依赖注入和依赖注入容器 为了降低代码耦合程度,提高项目的可维护性,Yii采用多许多当下最流行又相对成熟的设计模式,包括了依赖注入(Denpdency Injection, DI)和服务定位器(Serv ...
- YII框架的依赖注入容器与服务定位器简述
依赖注入容器 依赖注入(Dependency Injection,DI)容器就是一个对象use yii\di\Container,它知道怎样初始化并配置对象及其依赖的所有对象. 依赖注入和服务定位器都 ...
- 依赖注入容器Autofac的详解
Autofac和其他容器的不同之处是它和C#语言的结合非常紧密,在使用过程中对你的应用的侵入性几乎为零,更容易与第三方的组件集成,并且开源,Autofac的主要特性如下: 1,灵活的组件实例化:Aut ...
- Unity轻量级依赖注入容器
一.前言 Unity是一个轻量级的可扩展的依赖注入容器,支持构造函数,属性和方法调用注入.在Nuget里安装unity
- yii2之依赖注入与依赖注入容器
一.为什么需要依赖注入 首先我们先不管什么是依赖注入,先来分析一下没有使用依赖注入会有什么样的结果.假设我们有一个gmail邮件服务类GMail,然后有另一个类User,User类需要使用发邮件的功能 ...
- yii依赖注入和依赖注入容器
依赖注入和依赖注入容器¶ 为了降低代码耦合程度,提高项目的可维护性,Yii采用多许多当下最流行又相对成熟的设计模式,包括了依赖注入(Denpdency Injection, DI)和服务定位器(Ser ...
- php的依赖注入容器
这里接着上一篇 php依赖注入,直接贴出完整代码如下: <?php class C { public function doSomething() { echo __METHOD__, '我是C ...
- .net core从依赖注入容器获取对象
创建引擎方法:该方法用于在不使用构造注入的情况下从依赖注入容器中获取对象 /// <summary> /// 一个负责创建对象的引擎 /// </summary> public ...
- IoC 依赖注入容器 Unity
原文:IoC 依赖注入容器 Unity IoC 是什么? 在软件工程领域,“控制反转(Inversion of Control,缩写为IoC)”是一种编程技术,表述在面向对象编程中,可描述为在编译时静 ...
随机推荐
- PS:抠图方法1(利用对比度ctrl+l)
PS:抠图方法1(利用对比度ctrl+l) 工具/原料 Photoshop.美女照片 方法/步骤 小编使用的是Photoshop cs5版本,大家使用其他版本都没有关系,界面略有不同,但操 ...
- STM32F10x_模拟I2C读写_硬件I2C读写
STM32F10x_模拟I2C读写EEPROM STM32F10x_硬件I2C读写EEPROM(标准外设库版本) STM32F10x_硬件I2C主从通信(轮询发送,中断接收)
- WPF中ListBox控件选择多个数据项
XAML: <Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft ...
- First()、FirstOrDefault()、Single() 和 SingleOrDefault()的区别
Enumerable.First() 方法:返回序列中的第一个元素,如果源序列为空,则抛异常. Enumerable.FirstOrDefault ()方法返回序列中的第一个元素:如果序列中不包含任何 ...
- web服务器小记
服务器分web服务器,应用程序服务器以及HTTP服务器,此处只解释web服务器,详情: http://www.cnblogs.com/zhaoyl/archive/2012/10/10/2718575 ...
- java获取数据库的列名、类型等信息
当你使用和学习JDK的时候,可以查看并学习它所提供给你的两个ResultSetMetaData 和DataBaseMetaData类的源码并很好的了解它们的实现原理和思路,JDBC中提供有两种源数据, ...
- st-Spanning Tree
st-Spanning Tree time limit per test 4 seconds memory limit per test 256 megabytes input standard in ...
- OpenGL ES着色器语言之静态使用(static use)和预处理
OpenGL ES着色器语言之静态使用(static use) 在OpenGL ES中有一个术语叫静态使用(static use),什么叫静态使用呢? 在写代码中,对于一个变量可能具有以下三种情况: ...
- Loadrunner之文件的下载(八)
老猪提供: https://mp.weixin.qq.com/s?__biz=MzIwOTMzNDEwNw==&mid=100000013&idx=1&sn=624f5bc74 ...
- L7,too late
words: parcel,包裹 detective,侦探 expect,期待 airfield,飞机起落的场地 guard,警戒,守卫,n precious,adj,珍贵的 stone,石头 exp ...