php - Dependency Injection依赖注入 和 自动加载 各自的优缺点
ioc/di和自动加载时两回事。

ioc/di 让代码由创建对象改为注入对象,是一种编程思想,而自动加载,只是省略reqire文件而已。

ioc/di我认为有以下好处:

第一,把对象的创建从业务代码里抽出来。

第二,统一一个对象的创建方式,避免到处使用自己的方式创建对象。

第三,使用了建造者模式,将某些对象复杂的建造过程封装起来。

ioc/di我认为有以下坏处:

第一:滥用使得代码无法被跟踪到,我一个类的一个方法,在项目哪个地方用到了,ioc/di提供了一种途径,也是现在很多框架使用的途径,让这种代码已经无法跟踪到了。

第二:硬编码,我传一个字符串获取一个对象,如果以后这种对应关系不在或发生变化了怎么办。

第三:代码混乱,你会发现ioc/di不止可以返回一个对象,一个函数,甚至可以执行一个命令,然后锤子钉子锤子钉子。。。。。。

思想

思想是解决问题的根本
思想必须转换成习惯
构建一套完整的思想体系是开发能力成熟的标志
——《简单之美》(前言)

.

“成功的软件项目就是那些提交产物达到或超出客户的预期的项目,而且开发过程符合时间和费用上的要求,结果在面对变化和调整时有弹性。”
——《面向对象分析与设计》(第3版)P.236

术语介绍

——引用《Spring 2.0 技术手册》林信良

非侵入性 No intrusive

  • 框架的目标之一是非侵入性(No intrusive)

  • 组件可以直接拿到另一个应用或框架之中使用

  • 增加组件的可重用性(Reusability)

容器(Container)

  • 管理对象的生成、资源取得、销毁等生命周期

  • 建立对象与对象之间的依赖关系

  • 启动容器后,所有对象直接取用,不用编写任何一行代码来产生对象,或是建立对象之间的依赖关系。

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();

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

参考文章

补充

很多代码背后,都是某种哲学思想的体现。

以下引用《面向模式的软件架构》卷1模式系统第六章模式与软件架构

软件架构支持技术(开发软件时要遵循的基本原则)

  1. 抽象

  2. 封装

  3. 信息隐藏

  4. 分离关注点

  5. 耦合与内聚

  6. 充分、完整、简单

  7. 策略与实现分离

    • 策略组件负责上下文相关决策,解读信息的语义和含义,将众多不同结果合并或选择参数值

    • 实现组件负责执行定义完整的算法,不需要作出与上下文相关的决策。上下文和解释是外部的,通常由传递给组件的参数提供。

  8. 接口与实现分离

    • 接口部分定义了组件提供的功能以及如何使用该组件。组件的客户端可以访问该接口。

    • 实现部分包含实现组件提供的功能的实际代码,还可能包含仅供组件内部使用的函数和数据结构。组件的客户端不能访问其实现部分。

  9. 单个引用点

    • 软件系统中的任何元素都应只声明和定义一次,避免不一致性问题。
      10. 分而治之

软件架构的非功能特性

  1. 可修改性

    • 可维护性

    • 可扩展性

    • 重组

    • 可移植性

  2. 互操作性

    • 与其它系统或环境交互

  3. 效率

  4. 可靠性

    • 容错:发生错误时确保行为正确并自行修复

    • 健壮性:对应用程序进行保护,抵御错误的使用方式和无效输入,确保发生意外错误时处于指定状态。

  5. 可测试性

  6. 可重用性

    • 通过重用开发软件

    • 开发软件时考虑重用

  • php
  • 架构模式
  • 设计模式
  • http://segmentfault.com/a/1190000002411255一个IOC的简单实例:http://www.jb51.net/article/56101.htm
    <?php
    
     class Container
    {
    protected $setings = array(); public function set($abstract, $concrete = null)
    {
    if ($concrete === null) {
    $concrete = $abstract;
    } $this->setings[$abstract] = $concrete;
    } public function get($abstract, $parameters = array())
    {
    if (!isset($this->setings[$abstract])) {
    return null;
    } return $this->build($this->setings[$abstract], $parameters);
    } public function build($concrete, $parameters)
    {
    if ($concrete instanceof Closure) {
    echo 'Closure';
    return $concrete($this, $parameters);
    }
    $reflector = new ReflectionClass($concrete); if (!$reflector->isInstantiable()) {
    throw new Exception("Class {$concrete} is not instantiable");
    } $constructor = $reflector->getConstructor(); if (is_null($constructor)) {
    return $reflector->newInstance();
    } $parameters = $constructor->getParameters();
    $dependencies = $this->getDependencies($parameters); return $reflector->newInstanceArgs($dependencies);
    } public function getDependencies($parameters)
    {
    $dependencies = array();
    foreach ($parameters as $parameter) {
    $dependency = $parameter->getClass();
    if ($dependency === null) {
    if ($parameter->isDefaultValueAvailable()) {
    $dependencies[] = $parameter->getDefaultValue();
    } else {
    throw new Exception("Can not be resolve class dependency {$parameter->name}");
    }
    } else {
    $dependencies[] = $this->get($dependency->name);
    }
    } return $dependencies;
    }
    } interface MyInterface{}
    class Foo implements MyInterface{}
    class Bar implements MyInterface{}
    class Baz
    {
    public function __construct(MyInterface $foo)
    {
    $this->foo = $foo;
    }
    } $container = new Container(); $container->set('Baz', 'Baz');
    //$this->settings['Baz']='Baz'; $container->set('MyInterface', 'Foo');
    //$this->settings['MyInterface'] = 'Foo';
    $baz = $container->get('Baz');
    //
    print_r($baz);
    $container->set('MyInterface', 'Bar');
    $baz = $container->get('Baz');
    print_r($baz);
    ?>

PHP程序员如何理解IoC/DI(转)的更多相关文章

  1. PHP程序员如何理解IoC/DI

    思想是解决问题的根本 思想必须转换成习惯构建一套完整的思想体系是开发能力成熟的标志 详情请点击

  2. PHP程序员如何理解依赖注入容器(dependency injection container)

    背景知识 传统的思路是应用程序用到一个Foo类,就会创建Foo类并调用Foo类的方法,假如这个方法内需要一个Bar类,就会创建Bar类并调用Bar类的方法,而这个方法内需要一个Bim类,就会创建Bim ...

  3. 深入理解IoC/DI

    ------------------------------------------------------------------------ 理解IoC/DI 1.控制反转 --> 谁控制谁 ...

  4. 如何理解IoC/DI

    IoC:Inversion of Control,控制反转DI:Dependency Injection,依赖注入 要理解上面两个概念,就必须搞清楚如下的问题: 参与者都有谁?依赖:谁依赖于谁?为什么 ...

  5. 程序员笔记|Spring IoC、面向切面编程、事务管理等Spring基本概念详解

    一.Spring IoC 1.1 重要概念 1)控制反转(Inversion of control) 控制反转是一种通过描述(在java中通过xml或者注解)并通过第三方去产生或获取特定对象的方式. ...

  6. Spring理解IOC,DI,AOP作用,概念,理解。

    IOC控制反转:创建实例对象的控制权从代码转换到Spring容器.实际就是在xml中配置.配置对象 实例化对象时,进行强转为自定义类型.默认返回类型是Object强类型. ApplicationCon ...

  7. 深入理解IoC和DI

    本文章转载自: https://segmentfault.com/a/1190000005602011 最近在研究php的lumen框架和phalcon框架,这两个框架的底层架构都用到了IoC,DI, ...

  8. 6. Laravel5学习笔记:IOC/DI的理解

    介绍 IOC 控制反转 Inversion of Control 依赖关系的转移 依赖抽象而非实践 DI 依赖注入 Dependency Injection 不必自己在代码中维护对象的依赖 容器自己主 ...

  9. Spring.Net---3、IoC/DI深入理解

    ------------------------------------------------------------------------ 理解IoC/DI 1.控制反转 --> 谁控制谁 ...

随机推荐

  1. Elasticsearch与RDS比较

    Elasticsearch是一个分布式,实时,全文搜索引擎.所有操作都是通过RESTful接口实现,其底层实现是基于Lucene全文搜索引擎.数据以JSON文档的格式存储索引,不需要预先规定范式. 和 ...

  2. ConcurrentHashMap vs Collections.synchronizedMap()不同

    之前项目中,有用到过Collections.synchronizedMap(),后面发现当并发数很多的时候,出现其他请求等待情况,因为synchronizedMap会锁住所有的资源,后面通过查阅资料, ...

  3. mysql主从复制配置问题

    一,基本步骤 1,创建在主从数据上都创建复制账号,权限选上super, replication slave , replication master(选上这个可以方便从库变成主库): 2,配置主库和备 ...

  4. 还没被玩坏的robobrowser(6)——follow_link

    背景 在做spider的时候,我们经常会有点击链接的需求. 考虑这样的一个简单spider:获取qq.com主页上的今日话题中的内容. 一般思路是先去qq.com首页上找到今日话题的链接,然后点击这个 ...

  5. 你应该知道的JAVA面试题

    你应该知道的JAVA面试题 经常面试一些候选人,整理了下我面试使用的题目,陆陆续续整理出来的题目很多,所以每次会抽一部分来问.答案会在后面的文章中逐渐发布出来. 基础题目 Java线程的状态 进程和线 ...

  6. 【转载】centos7.3 防火墙配置

    firewalld介绍原文:https://www.cnblogs.com/moxiaoan/p/5683743.html 一. centos7 默认有一个防火墙 firewalld,具体使用如下: ...

  7. 使用layui 和 jquery 问题小结

    问题 1 在使用 layui 2.2.5 之前,可以引入最新版的 jquery ,使用更好的性能.也可以使用layui 的jquery内部版本.如果引入要在引入layui.js 之前引入 2 使用 s ...

  8. java中多个数字运算后值不对(失真)处理方法

    最近遇到一个bug ,在java里面计算两个数字相减,633011.20-31296.30 得到的结果居然是601714.8999999999,丢失精度了,原来这是Java浮点运算的一个bug. 解决 ...

  9. 使用OkHttpClient处理json请求处理的方式

    今天遇到一个问题,重构老系统时,前端传递的参数是一个json,controller层可以用@ResponseBody来接收. 因为新系统用的是spring cloud这一套,调用其他服务使用的是fei ...

  10. 如何改变git的默认路径

    1.win10下git默认启动路径是用户的根目录,东西太多太乱了. 2.修改很容易,右键单击桌面的快捷方式,选择“属性”. 3.删除“目录”中的 --cd-to-home 选项,再将“起始位置&quo ...