php - Dependency Injection依赖注入 和 自动加载 各自的优缺点
ioc/di和自动加载时两回事。 ioc/di 让代码由建立对象改成注入对象,是一种编程思想,而自动加载,只是省略reqire文件而已。 ioc/di我认为有如下好处: 第一,把对象的建立从业务代码里抽出来。 第二,统一一个对象的建立方式,避免处处使用本身的方式建立对象。 第三,使用了建造者模式,将某些对象复杂的建造过程封装起来。 ioc/di我认为有如下坏处: 第一:滥用使得代码没法被跟踪到,我一个类的一个方法,在项目哪一个地方用到了,ioc/di提供了一种途径,也是如今不少框架使用的途径,让这种代码已经没法跟踪到了。 第二:硬编码,我传一个字符串获取一个对象,若是之后这种对应关系不在或发生变化了怎么办。 第三:代码混乱,你会发现ioc/di不止能够返回一个对象,一个函数,甚至能够执行一个命令,而后锤子钉子锤子钉子。。。。。。
思想
思想是解决问题的根本
思想必须转换成习惯
构建一套完整的思想体系是开发能力成熟的标志
——《简单之美》(前言)php
.html
“成功的软件项目就是那些提交产物达到或超出客户的预期的项目,并且开发过程符合时间和费用上的要求,结果在面对变化和调整时有弹性。”
——《面向对象分析与设计》(第3版)P.236程序员
术语介绍
——引用《Spring 2.0 技术手册》林信良算法
非侵入性 No intrusive
-
框架的目标之一是非侵入性(No intrusive)数据库
-
组件能够直接拿到另外一个应用或框架之中使用编程
-
增长组件的可重用性(Reusability)segmentfault
容器(Container)
-
管理对象的生成、资源取得、销毁等生命周期设计模式
-
创建对象与对象之间的依赖关系服务器
-
启动容器后,全部对象直接取用,不用编写任何一行代码来产生对象,或是创建对象之间的依赖关系。session
IoC
-
控制反转 Inversion of Control
-
依赖关系的转移
-
依赖抽象而非实践
DI
-
依赖注入 Dependency Injection
-
没必要本身在代码中维护对象的依赖
-
容器自动根据配置,将依赖注入指定对象
AOP
-
Aspect-oriented programming
-
面向方面编程
-
无需修改任何一行程序代码,将功能加入至原先的应用程序中,也能够在不修改任何程序的状况下移除。
分层
表现层:提供服务,显示信息。
领域层:逻辑,系统中真正的核心。
数据源层:与数据库、消息系统、事务管理器及其它软件包通讯。
——《企业应用架构模式》P.14
代码演示IoC
假设应用程序有储存需求,若直接在高层的应用程序中调用低层模块API,致使应用程序对低层模块产生依赖。
/** * 高层 */ class Business { private $writer; public function __construct() { $this->writer = new FloppyWriter(); } public function save() { $this->writer->saveToFloppy(); } } /** * 低层,软盘存储 */ class FloppyWriter { public function saveToFloppy() { echo __METHOD__; } } $biz = new Business(); $biz->save(); // FloppyWriter::saveToFloppy
假设程序要移植到另外一个平台,而该平台使用USB磁盘做为存储介质,则这个程序没法直接重用,必须加以修改才行。本例因为低层变化致使高层也跟着变化,很差的设计。
正如前方提到的
控制反转 Inversion of Control
依赖关系的转移
依赖抽象而非实践
程序不该该依赖于具体的实现,而是要依赖抽像的接口。请看代码演示
/** * 接口 */ interface IDeviceWriter { public function saveToDevice(); } /** * 高层 */ class Business { /** * @var IDeviceWriter */ private $writer; /** * @param IDeviceWriter $writer */ public function setWriter($writer) { $this->writer = $writer; } public function save() { $this->writer->saveToDevice(); } } /** * 低层,软盘存储 */ class FloppyWriter implements IDeviceWriter { public function saveToDevice() { echo __METHOD__; } } /** * 低层,USB盘存储 */ class UsbDiskWriter implements IDeviceWriter { public function saveToDevice() { echo __METHOD__; } } $biz = new Business(); $biz->setWriter(new UsbDiskWriter()); $biz->save(); // UsbDiskWriter::saveToDevice $biz->setWriter(new FloppyWriter()); $biz->save(); // FloppyWriter::saveToDevice
控制权从实际的FloppyWriter转移到了抽象的IDeviceWriter接口上,让Business依赖于IDeviceWriter接口,且FloppyWriter、UsbDiskWriter也依赖于IDeviceWriter接口。
这就是IoC,面对变化,高层不用修改一行代码,再也不依赖低层,而是依赖注入,这就引出了DI。
比较实用的注入方式有三种:
-
Setter injection 使用setter方法
-
Constructor injection 使用构造函数
-
Property Injection 直接设置属性
事实上无论有多少种方法,都是IoC思想的实现而已,上面的代码演示的是Setter方式的注入。
依赖注入容器 Dependency Injection Container
-
管理应用程序中的『全局』对象(包括实例化、处理依赖关系)。
-
能够延时加载对象(仅用到时才建立对象)。
-
促进编写可重用、可测试和松耦合的代码。
理解了IoC和DI以后,就引起了另外一个问题,引用Phalcon文档描述以下:
若是这个组件有不少依赖, 咱们须要建立多个参数的setter方法来传递依赖关系,或者创建一个多个参数的构造函数来传递它们,另外在使用组件前还要每次都建立依赖,这让咱们的代码像这样不易维护
//建立依赖实例或从注册表中查找 $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。为了解决这样的问题,咱们再次回到全局注册表建立组件。无论怎么样,在建立对象以前,它增长了一个新的抽象层:
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); } }
瞬间,咱们又回到刚刚开始的问题了,咱们再次建立依赖实例在组件内部!咱们能够继续前进,找出一个每次能奏效的方法去解决这个问题。但彷佛一次又一次,咱们又回到了不实用的例子中。
一个实用和优雅的解决方法,是为依赖实例提供一个容器。这个容器担任全局的注册表,就像咱们刚才看到的那样。使用依赖实例的容器做为一个桥梁来获取依赖实例,使咱们可以下降咱们的组件的复杂性:
class SomeComponent { protected $_di; public function __construct($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 Phalcon\DI(); //在容器中注册一个db服务 $di->set('db', function() { return new Connection(array( "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->someTask();
这个组件如今能够很简单的获取到它所须要的服务,服务采用延迟加载的方式,只有在须要使用的时候才初始化,这也节省了服务器资源。这个组件如今是高度解耦。例如,咱们能够替换掉建立链接的方式,它们的行为或它们的任何其余方面,也不会影响该组件。
参考文章
-
Inversion of Control Containers and the Dependency Injection pattern by Martin Fowler
补充
不少代码背后,都是某种哲学思想的体现。
如下引用《面向模式的软件架构》卷1模式系统第六章模式与软件架构
软件架构支持技术(开发软件时要遵循的基本原则)
-
抽象
-
封装
-
信息隐藏
-
分离关注点
-
耦合与内聚
-
充分、完整、简单
-
策略与实现分离
-
策略组件负责上下文相关决策,解读信息的语义和含义,将众多不一样结果合并或选择参数值
-
实现组件负责执行定义完整的算法,不须要做出与上下文相关的决策。上下文和解释是外部的,一般由传递给组件的参数提供。
-
-
接口与实现分离
-
接口部分定义了组件提供的功能以及如何使用该组件。组件的客户端能够访问该接口。
-
实现部分包含实现组件提供的功能的实际代码,还可能包含仅供组件内部使用的函数和数据结构。组件的客户端不能访问其实现部分。
-
-
单个引用点
-
软件系统中的任何元素都应只声明和定义一次,避免不一致性问题。
10. 分而治之
-
软件架构的非功能特性
-
可修改性
-
可维护性
-
可扩展性
-
重组
-
可移植性
-
-
互操做性
-
与其它系统或环境交互
-
-
效率
-
可靠性
-
容错:发生错误时确保行为正确并自行修复
-
健壮性:对应用程序进行保护,抵御错误的使用方式和无效输入,确保发生意外错误时处于指定状态。
-
-
可测试性
-
可重用性
-
经过重用开发软件
-
开发软件时考虑重用
-