友情提示:本文有点长,但绝对都是干货,请耐心读完,必有收获!
容器,字面上理解就是装东西的东西。常见的变量、对象属性等都可以算是容器。一个容器能够装什么,全部取决于你对该容器的定义。当然,有这样一种容器,它存放的不是文本、数值,而是对象、对象的描述(类、接口)或者是提供对象的回调,通过这种容器,我们得以实现许多高级的功能,其中最常提到的,就是 “解耦” 、“依赖注入(DI)”。本文就从这里开始。
IoC 容器 —— Laravel 的核心
Laravel 的核心就是一个 IoC 容器,根据文档,称其为“服务容器”,顾名思义,该容器提供了整个框架中需要的一系列服务。作为初学者,很多人会在这一个概念上犯难,因此,我打算从一些基础的内容开始讲解,通过理解面向对象开发中依赖的产生和解决方法,来逐渐揭开“依赖注入”的面纱,逐渐理解这一神奇的设计理念。
本文一大半内容都是通过举例来让读者去理解什么是 IoC(控制反转) 和 DI(依赖注入),通过理解这些概念,来更加深入。更多关于 laravel 服务容器的用法建议阅读文档即可。
IoC 容器诞生的故事
讲解 IoC 容器有很多的文章,我之前也写过。但现在我打算利用当下的灵感重新来过,那么开始吧。
超人和超能力,依赖的产生
面向对象编程,有以下几样东西无时不刻的接触:接口、类还有对象。这其中,接口是类的原型,一个类必须要遵守其实现的接口;对象则是一个类实例化后的产物,我们称其为一个实例。当然这样说肯定不利于理解,我们就实际的写点中看不中用的代码辅助学习。
怪物横行的世界,总归需要点超级人物来摆平。
我们把一个“超人”作为一个类,
class Superman {}
我们可以想象,一个超人诞生的时候肯定拥有至少一个超能力,这个超能力也可以抽象为一个对象,为这个对象定义一个描述他的类吧。一个超能力肯定有多种属性、(操作)方法,这个尽情的想象,但是目前我们先大致定义一个只有属性的“超能力”,至于能干啥,我们以后再丰富:
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) {} }
为了省事儿我没有详细写出 __construct()
这个构造函数的全部,只写了需要传递的参数。
好了,这下我们的超人有点“忙”了。在超人初始化的时候,我们会根据需要来实例化其拥有的超能力吗,大致如下:
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, 50, 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, 50, 2]); /* $this->power = array( $factory->makeModule('Force', [45]), $factory->makeModule('Shot', [99, 50, 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 容器的重要组成 —— 依赖注入
由 “超人” 对 “超能力” 的依赖变成 “超人” 对 “超能力模组工厂” 的依赖后,对付小怪兽们变得更加得心应手。但这也正如你所看到的,依赖并未解除,只是由原来对多个外部的依赖变成了对一个 “工厂” 的依赖。假如工厂出了点麻烦,问题变得就很棘手。
Mac Intellij Idea get/set方法快捷键那我就告诉你吧,Intellij Idea get/set方法快捷键,完全和window不一样。
Vue路由的$router.back(-1)回退时如何判断有没有上一个路由这个函数是这样的,判断当前页面的历史记录是不是小于等于1,如果小于等于1,说明这个页面没有可以返回的上一页,如果没有可以返回的上一页,就给地址栏加上一个goindex=true的参数,这样你从这个页面在往下一个页面跳转在返回,这个参数就一直加上的
element el-date-picker 后退8小时的问题 和 new Date()后退8小时的问题选择8月1号00:00:00后取得值为2018-07-31T16:00:00+08:00,后退了8小时,增加下面这行代码就显示正常了。
Mac 查看本机ip地址记住看好下面,查询mac的本机地址是ifconfig而不是ipconfig,windows才是ipconfig,虽然不知道if是啥意思。
mybatis foreach标签详解,逗号,and还有or,留一个查询数据库中文章的相关文章 文章为一个表 字段tags为相关文章字符串中间用','逗号进行啦分割 查询完一个文章后可以把tags字段构造为一个List<String> 然后利用这个集合作为条件来查询
MyBatis传入多个参数的问题MyBatis传入多个参数的问题
关于springboot整合配置pagehelper插件的方法pagehelper设置后,超过分页设置,总是显示最后一也的数据,应该怎么设置。
env: /etc/init.d/redis: Permission deniedenv: /etc/init.d/redis: Permission denied,chmod a+x /etc/init.d/redis
Failed to start ....service: Unit not found.开始service失败,Unit找不到。
genymotion免费版genymotion是一套完整的android虚拟环境工具,相对官方android sdk,它的安装和使用简洁方便不臃肿,但是现在genymotion官方网站不再提供个人的下载,这篇博客记录了如何下载和使用genymotion个人版.