PHP程序员如何理解IoC/DI【转载】

2024-06-09 21:38
文章标签 程序员 php 理解 ioc di 转载

本文主要是介绍PHP程序员如何理解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();

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

参考文章

补充

php程序员即不要陷入java程序员过度设计的泥潭,也不要过度追求所谓的简单,实际上是幼稚。很多代码背后,都是某种哲学思想的体现。我们中国人是最讲究尊师重道的,先求理解,再求被理解,没有继承何来创新。

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

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

  1. 抽象
  2. 封装
  3. 信息隐藏
  4. 分离关注点
  5. 耦合与内聚
  6. 充分、完整、简单
  7. 策略与实现分离

    • 策略组件负责上下文相关决策,解读信息的语义和含义,将众多不同结果合并或选择参数值
    • 实现组件负责执行定义完整的算法,不需要作出与上下文相关的决策。上下文和解释是外部的,通常由传递给组件的参数提供。
  8. 接口与实现分离

    • 接口部分定义了组件提供的功能以及如何使用该组件。组件的客户端可以访问该接口。
    • 实现部分包含实现组件提供的功能的实际代码,还可能包含仅供组件内部使用的函数和数据结构。组件的客户端不能访问其实现部分。
  9. 单个引用点

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

软件架构的非功能特性

  1. 可修改性

    • 可维护性
    • 可扩展性
    • 重组
    • 可移植性
  2. 互操作性

    • 与其它系统或环境交互
  3. 效率
  4. 可靠性

    • 容错:发生错误时确保行为正确并自行修复
    • 健壮性:对应用程序进行保护,抵御错误的使用方式和无效输入,确保发生意外错误时处于指定状态。
  5. 可测试性
  6. 可重用性

    • 通过重用开发软件
    • 开发软件时考虑重用

这篇关于PHP程序员如何理解IoC/DI【转载】的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1046372

相关文章

深入理解C++ 空类大小

《深入理解C++空类大小》本文主要介绍了C++空类大小,规定空类大小为1字节,主要是为了保证对象的唯一性和可区分性,满足数组元素地址连续的要求,下面就来了解一下... 目录1. 保证对象的唯一性和可区分性2. 满足数组元素地址连续的要求3. 与C++的对象模型和内存管理机制相适配查看类对象内存在C++中,规

PHP执行php.exe -v命令报错的解决方案

《PHP执行php.exe-v命令报错的解决方案》:本文主要介绍PHP执行php.exe-v命令报错的解决方案,文中通过图文讲解的非常详细,对大家的学习或工作有一定的帮助,需要的朋友可以参考下... 目录执行phpandroid.exe -v命令报错解决方案执行php.exe -v命令报错-PHP War

认识、理解、分类——acm之搜索

普通搜索方法有两种:1、广度优先搜索;2、深度优先搜索; 更多搜索方法: 3、双向广度优先搜索; 4、启发式搜索(包括A*算法等); 搜索通常会用到的知识点:状态压缩(位压缩,利用hash思想压缩)。

【生成模型系列(初级)】嵌入(Embedding)方程——自然语言处理的数学灵魂【通俗理解】

【通俗理解】嵌入(Embedding)方程——自然语言处理的数学灵魂 关键词提炼 #嵌入方程 #自然语言处理 #词向量 #机器学习 #神经网络 #向量空间模型 #Siri #Google翻译 #AlexNet 第一节:嵌入方程的类比与核心概念【尽可能通俗】 嵌入方程可以被看作是自然语言处理中的“翻译机”,它将文本中的单词或短语转换成计算机能够理解的数学形式,即向量。 正如翻译机将一种语言

【C++高阶】C++类型转换全攻略:深入理解并高效应用

📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C++ “ 登神长阶 ” 🤡往期回顾🤡:C++ 智能指针 🌹🌹期待您的关注 🌹🌹 ❀C++的类型转换 📒1. C语言中的类型转换📚2. C++强制类型转换⛰️static_cast🌞reinterpret_cast⭐const_cast🍁dynamic_cast 📜3. C++强制类型转换的原因📝

深入理解RxJava:响应式编程的现代方式

在当今的软件开发世界中,异步编程和事件驱动的架构变得越来越重要。RxJava,作为响应式编程(Reactive Programming)的一个流行库,为Java和Android开发者提供了一种强大的方式来处理异步任务和事件流。本文将深入探讨RxJava的核心概念、优势以及如何在实际项目中应用它。 文章目录 💯 什么是RxJava?💯 响应式编程的优势💯 RxJava的核心概念

如何通俗理解注意力机制?

1、注意力机制(Attention Mechanism)是机器学习和深度学习中一种模拟人类注意力的方法,用于提高模型在处理大量信息时的效率和效果。通俗地理解,它就像是在一堆信息中找到最重要的部分,把注意力集中在这些关键点上,从而更好地完成任务。以下是几个简单的比喻来帮助理解注意力机制: 2、寻找重点:想象一下,你在阅读一篇文章的时候,有些段落特别重要,你会特别注意这些段落,反复阅读,而对其他部分

深入理解数据库的 4NF:多值依赖与消除数据异常

在数据库设计中, "范式" 是一个常常被提到的重要概念。许多初学者在学习数据库设计时,经常听到第一范式(1NF)、第二范式(2NF)、第三范式(3NF)以及 BCNF(Boyce-Codd范式)。这些范式都旨在通过消除数据冗余和异常来优化数据库结构。然而,当我们谈到 4NF(第四范式)时,事情变得更加复杂。本文将带你深入了解 多值依赖 和 4NF,帮助你在数据库设计中消除更高级别的异常。 什么是

分布式系统的个人理解小结

分布式系统:分的微小服务,以小而独立的业务为单位,形成子系统。 然后分布式系统中需要有统一的调用,形成大的聚合服务。 同时,微服务群,需要有交流(通讯,注册中心,同步,异步),有管理(监控,调度)。 对外服务,需要有控制的对外开发,安全网关。

Java IO 操作——个人理解

之前一直Java的IO操作一知半解。今天看到一个便文章觉得很有道理( 原文章),记录一下。 首先,理解Java的IO操作到底操作的什么内容,过程又是怎么样子。          数据来源的操作: 来源有文件,网络数据。使用File类和Sockets等。这里操作的是数据本身,1,0结构。    File file = new File("path");   字