IOC控制反转和DI依赖注入
前言有这样一种容器,它存放的是对象、对象的描述(类、接口)或者是提供对象的回调,通过这种容器,我们得以实现许多高级的功能,其中最常提到的,就是 “解耦” 、“依赖注入(DI)”。本文就从这里开始。 IoC 容器, laravel 的核心Laravel(音 [‘l?r?vel],”来若外偶“)的核心就是一个 IoC 容器 ,根据文档,称其为“ 服务容器 ”,顾名思义,该容器提供了整个框架中需要的一系列服务。作为初学者,很多人会在这一个概念上犯难,因此,我打算从一些基础的内容开始讲解,通过理解面向对象开发中依赖的产生和解决方法,来逐渐揭开“依赖注入”的面纱,逐渐理解这一神奇的设计理念。 本文一大半内容都是通过举例来让读者去理解什么是 IoC(控制反转) 和 DI(依赖注入),通过理解这些概念,来更加深入。更多关于 laravel 服务容器的用法建议阅读文档即可。 IoC 容器诞生的故事面向对象编程,有以下几样东西无时不刻的接触:接口、类还有对象。这其中,接口是类的原型,一个类必须要遵守其实现的接口;对象则是一个类实例化后的产物,我们称其为一个实例。当然这样说肯定不利于理解,我们就实际的写点中看不中用的代码辅助学习。
我们把一个“超人”作为一个类,
我们可以想象,一个超人诞生的时候肯定拥有至少一个超能力,这个超能力也可以抽象为一个对象,为这个对象定义一个描述他的类吧。一个超能力肯定有多种属性、(操作)方法,这个尽情的想象,但是目前我们先大致定义一个只有属性的“超能力”,至于能干啥,我们以后再丰富: class Power {
/** * 能力值 */
protected $ability;
/** * 能力范围或距离 */
protected $range;
public function __construct($ability,$range)
{
$this->ability = $ability;
$this->range = $range;
}
}
这时候我们回过头,修改一下之前的“超人”类,让一个“超人”创建的时候被赋予一个超能力: class Superman {
protected $power;
public function __construct() {
$this->power = new Power(999,100);
}
}
这样的话,当我们创建一个“超人”实例的时候,同时也创建了一个“超能力”的实例,但是,我们看到了一点,“超人”和“超能力”之间不可避免的产生了一个依赖。 在一个贯彻面向对象编程的项目中,这样的依赖随处可见。少量的依赖并不会有太过直观的影响,我们随着这个例子逐渐铺开,让大家慢慢意识到,当依赖达到一个量级时,是怎样一番噩梦般的体验。当然,我也会自然而然的讲述如何解决问题。 一堆乱麻 —— 可怕的依赖之前的例子中,超能力类实例化后是一个具体的超能力,但是我们知道,超人的超能力是多元化的,每种超能力的方法、属性都有不小的差异,没法通过一种类描述完全。我们现在进行修改,我们假设超人可以有以下多种超能力:
我们创建了如下类: class Flight {
protected $speed;
protected $holdtime;
public function __construct($speed,$holdtime) {//...}
}
class Force {
protected $force;
public function __construct($force) {//...}
}
class Shot {
protected $atk;
protected $range;
protected $limit;
public function __construct($atk,$range,$limit) {//...}
}
好了,这下我们的超人有点“忙”了。在超人初始化的时候,我们会根据需要来实例化其拥有的超能力吗,大致如下 class Superman {
protected $power;
public function __construct() {
$this->power = new Fight(9,100);
// $this->power = new Force(45);
// $this->power = new Shot(99,50,2);
/* $this->power = array( new Force(45),new Shot(99,2) ); */
}
}
我们需要自己手动的在构造函数内(或者其他方法里)实例化一系列需要的类,这样并不好。可以想象,假如需求变更(不同的怪物横行地球),需要更多的有针对性的 新的 超能力,或者需要 变更 超能力的方法,我们必须 重新改造 超人。换句话说就是,改变超能力的同时,我还得重新制造个超人。效率太低了! 这时,灵机一动的人想到:为什么不可以这样呢?超人的能力可以被随时更换,只需要添加或者更新一个芯片或者其他装置啥的(想到钢铁侠没)。这样的话就不要整个重新来过了。 对,就是这样的。 我们不应该手动在 “超人” 类中固化了他的 “超能力” 初始化的行为,而转由外部负责,由外部创造超能力模组、装置或者芯片等(我们后面统一称为 “模组”),植入超人体内的某一个接口,这个接口是一个既定的,只要这个 “模组” 满足这个接口的装置都可以被超人所利用,可以提升、增加超人的某一种能力。这种由外部负责其依赖需求的行为,我们可以称其为 “ 控制反转 (IoC) ”。 工厂模式,依赖转移当然,实现控制反转的方法有几种。在这之前,不如我们先了解一些好玩的东西。 我们可以想到,组件、工具(或者超人的模组),是一种可被生产的玩意儿,生产的地方当然是 “工厂(Factory)”,于是有人就提出了这样一种模式: 工厂模式 。 我们为了给超人制造超能力模组,我们创建了一个工厂,它可以制造各种各样的模组,且仅需要通过一个方法: class SuperModuleFactory {
public function makeModule($moduleName,$options) {
switch ($moduleName) {
case 'Fight': return new Fight($options[0],$options[1]);
case 'Force': return new Force($options[0]);
case 'Shot': return new Shot($options[0],$options[1],$options[2]);
}
}
}
这时候,超人 创建之初就可以使用这个工厂! class Superman {
protected $power;
public function __construct() {
// 初始化工厂
$factory = new SuperModuleFactory;
// 通过工厂提供的方法制造需要的模块
$this->power = $factory->makeModule('Fight',[9,100]);
// $this->power = $factory->makeModule('Force',[45]);
// $this->power = $factory->makeModule('Shot',[99,2]);
/* $this->power = array( $factory->makeModule('Force',[45]),$factory->makeModule('Shot',2]) ); */
}
}
可以看得出,我们不再需要在超人初始化之初,去初始化许多第三方类,只需初始化一个工厂类,即可满足需求。但这样似乎和以前区别不大,只是没有那么多 new 关键字。其实我们稍微改造一下这个类,你就明白,工厂类的真正意义和价值了。 class Superman {
protected $power;
public function __construct(array $modules) {
// 初始化工厂
$factory = new SuperModuleFactory;
// 通过工厂提供的方法制造需要的模块
foreach ($modules as $moduleName => $moduleOptions) {
$this->power[] = $factory->makeModule($moduleName,$moduleOptions);
}
}
}
// 创建超人
$superman = new Superman([
'Fight' => [9,100],'Shot' => [99,50,2]
]);
现在修改的结果令人满意。现在,“超人” 的创建不再依赖任何一个 “超能力” 的类,我们如若修改了或者增加了新的超能力,只需要针对修改 SuperModuleFactory 即可。扩充超能力的同时不再需要重新编辑超人的类文件,使得我们变得很轻松。但是,这才刚刚开始。 再进一步!IoC 容器的重要组成 —— 依赖注入大多数情况下,工厂模式已经足够了。工厂模式的缺点就是:接口未知(即没有一个很好的契约模型,关于这个我马上会有解释)、产生对象类型单一。总之就是,还是不够灵活。虽然如此,工厂模式依旧十分优秀,并且适用于绝大多数情况。不过我们为了讲解后面的 依赖注入 ,这里就先夸大一下工厂模式的缺陷咯。 我们知道,超人依赖的模组,我们要求有统一的接口,这样才能和超人身上的注入接口对接,最终起到提升超能力的效果。 事实上,我之前说谎了,不仅仅只有一堆小怪兽,还有更多的大怪兽。嘿嘿。额,这时候似乎工厂的生产能力显得有些不足 —— 由于工厂模式下,所有的模组都已经在工厂类中安排好了,如果有新的、高级的模组加入,我们必须修改工厂类(好比增加新的生产线): class SuperModuleFactory {
public function makeModule($moduleName,$options[2]);
// case 'more': .......
// case 'and more': .......
// case 'and more': .......
// case 'oh no! its too many!': .......
}
}
}
看到没。。。噩梦般的感受! 由于对超能力模组的需求不断增大,我们需要集合整个世界的高智商人才,一起解决问题,不应该仅仅只有几个工厂垄断负责。不过高智商人才们都非常自负,认为自己的想法是对的,创造出的超能力模组没有统一的接口,自然而然无法被正常使用。这时我们需要提出一种契约,这样无论是谁创造出的模组,都符合这样的接口,自然就可被正常使用。 interface SuperModuleInterface
{
/** * 超能力激活方法 * * 任何一个超能力都得有该方法,并拥有一个参数 *@param array $target 针对目标,可以是一个或多个,自己或他人 */
public function activate(array $target);
}
这时候,那些提出更好的超能力模组的高智商人才,遵循这个接口,创建了下述(模组)类: /** * X-超能量 */
class XPower implements SuperModuleInterface {
public function activate(array $target) {
// 这只是个例子。。具体自行脑补
}
}
/** * 终极炸弹 (就这么俗) */
class UltraBomb implements SuperModuleInterface {
public function activate(array $target) {
// 这只是个例子。。具体自行脑补
}
}
同时,为了防止有些 “砖家” 自作聪明,或者一些叛徒恶意捣蛋,不遵守契约胡乱制造模组,影响超人,我们对超人初始化的方法进行改造 class Superman {
protected $module;
public function __construct(SuperModuleInterface $module) {
$this->module = $module
}
}
改造完毕!现在,当我们初始化 “超人” 类的时候,提供的模组实例必须是一个 SuperModuleInterface 接口的实现。否则就会提示错误。 本文从开头到现在提到的一系列依赖,只要不是由内部生产(比如初始化、构造函数 __construct 中通过工厂方法、自行手动 new 的),而是由外部以参数或其他形式注入的,都属于 依赖注入(DI) 。是不是豁然开朗?事实上,就是这么简单。下面就是一个典型的 依赖注入 : // 超能力模组
$superModule = new XPower;
// 初始化一个超人,并注入一个超能力模组依赖
$superMan = new Superman($superModule); (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
- 微信小程序开发--从block盒式布局到Flex弹性布局
- c – 如何在基于GStreamer的Qt中实现视频窗口小部件?
- javascript请求servlet实现ajax示例(分享)
- 文件上传利器SWFUpload入门简易教程
- c – 在Qt Qml控件中,ApplicationWindow在运行时缺少原生主
- 配置iis6,iis7.5支持解析.json格式文件的方法
- ORACLE密码错误验证延迟
- 计算机数据存储模型
- Logdump Reference for Oracle GoldenGate12c (12.2.0.1)中
- application:continueUserActivity:restorationHandler: