一、名词介绍
laravel官网介绍
Laravel使用IoC(Inversion of Control,控制倒转,这是一个设计模式,可以先查看下百科)容器这个强有力的工具管理类依赖。依赖注入(也是一种设计模式,一般用于实现IoC)是一个不用编写固定代码来处理类之间依赖的方法,相反的,这些依赖是在运行时注入的,这样允许处理依赖时具有更大的灵活性。
简单粗暴的理解:
依赖注入和控制反转说的是同一个东西,是一种设计模式,这种设计模式用来减少程序间的耦合。
二、IOC的理论背景
我们知道在面向对象设计的软件系统中,它的底层都是由N个对象构成的,各个对象之间通过相互合作,最终实现系统地业务逻辑[1]。
图1 软件系统中耦合的对象
如果我们打开机械式手表的后盖,就会看到与上面类似的情形,各个齿轮分别带动时针、分针和秒针顺时针旋转,从而在表盘上产生正确的时间。图1中描述的就是这样的一个齿轮组,它拥有多个独立的齿轮,这些齿轮相互啮合在一起,协同工作,共同完成某项任务。我们可以看到,在这样的齿轮组中,如果有一个齿轮出了问题,就可能会影响到整个齿轮组的正常运转。
齿轮组中齿轮之间的啮合关系,与软件系统中对象之间的耦合关系非常相似。对象之间的耦合关系是无法避免的,也是必要的,这是协同工作的基础。现在,伴随着工业级应用的规模越来越庞大,对象之间的依赖关系也越来越复杂,经常会出现对象之间的多重依赖性关系,因此,架构师和设计师对于系统的分析和设计,将面临更大的挑战。对象之间耦合度过高的系统,必然会出现牵一发而动全身的情形。
图2 对象之间的依赖关系
耦合关系不仅会出现在对象与对象之间,也会出现在软件系统的各模块之间,以及软件系统和硬件系统之间。如何降低系统之间、模块之间和对象之间的耦合度,是软件工程永远追求的目标之一。为了解决对象之间的耦合度过高的问题,软件专家Michael Mattson 1996年提出了IOC理论,用来实现对象之间的“解耦”,目前这个理论已经被成功地应用到实践当中。
IOC理论提出的观点大体是这样的:借助于“第三方”实现具有依赖关系的对象之间的解耦。如下图:
图3 IOC解耦过程
大家看到了吧,由于引进了中间位置的“第三方”,也就是IOC容器,使得A、B、C、D这4个对象没有了耦合关系,齿轮之间的传动全部依靠“第三方”了,全部对象的控制权全部上缴给“第三方”IOC容器,所以,IOC容器成了整个系统的关键核心,它起到了一种类似“粘合剂”的作用,把系统中的所有对象粘合在一起发挥作用,如果没有这个“粘合剂”,对象与对象之间会彼此失去联系,这就是有人把IOC容器比喻成“粘合剂”的由来。
我们再来做个试验:把上图中间的IOC容器拿掉,然后再来看看这套系统:
图4 拿掉IOC容器后的系统
我们现在看到的画面,就是我们要实现整个系统所需要完成的全部内容。这时候,A、B、C、D这4个对象之间已经没有了耦合关系,彼此毫无联系,这样的话,当你在实现A的时候,根本无须再去考虑B、C和D了,对象之间的依赖关系已经降低到了最低程度。所以,如果真能实现IOC容器,对于系统开发而言,这将是一件多么美好的事情,参与开发的每一成员只要实现自己的类就可以了,跟别人没有任何关系!
我们再来看看,控制反转(IOC)到底为什么要起这么个名字?我们来对比一下:
软件系统在没有引入IOC容器之前,如图1所示,对象A依赖于对象B,那么对象A在初始化或者运行到某一点的时候,自己必须主动去创建对象B或者使用已经创建的对象B。无论是创建还是使用对象B,控制权都在自己手上。
软件系统在引入IOC容器之后,这种情形就完全改变了,如图3所示,由于IOC容器的加入,对象A与对象B之间失去了直接联系,所以,当对象A运行到需要对象B的时候,IOC容器会主动创建一个对象B注入到对象A需要的地方。
通过前后的对比,我们不难看出来:对象A获得依赖对象B的过程,由主动行为变为了被动行为,控制权颠倒过来了,这就是“控制反转”这个名称的由来。
三、示例演示
1、先假设我们这里有一个类,类里面需要用到数据库连接,按照最最原始的办法,我们可能是这样写这个类的:
过程:
在构造函数里先将数据库类文件include进来;
然后又通过new Db并传入数据库连接信息实例化db类;
之后getList方法就可以通过$this->_db来调用数据库类,实现数据库操作。
看上去我们实现了想要的功能,但是这是一个噩梦的开始,以后example1,example2,example3....越来越多的类需要用到db组件,如果都这么写的话,万一有一天数据库密码改了或者db类发生变化了,岂不是要回头修改所有类文件?
ok,为了解决这个问题,工厂模式出现了,我们创建了一个Factory方法,并通过Factory::getDb()方法来获得db组件的实例:
2、sample类变成:
这样就完美了吗?再次想想一下以后example1,example2,example3....所有的类,你都需要在构造函数里通过Factory::getDb();获的一个Db实例,实际上你由原来的直接与Db类的耦合变为了和Factory工厂类的耦合,工厂类只是帮你把数据库连接信息给包装起来了,虽然当数据库信息发生变化时只要修改Factory::getDb()方法就可以了,但是突然有一天工厂方法需要改名,或者getDb方法需要改名,你又怎么办?当然这种需求其实还是很操蛋的,但有时候确实存在这种情况,一种解决方式是:
3、我们不从example类内部实例化Db组件,我们依靠从外部的注入,什么意思呢?看下面的例子:
这样一来,example类完全与外部类解除耦合了,你可以看到Db类里面已经没有工厂方法或Db类的身影了。我们通过从外部调用example类的setDb方法,将连接实例直接注入进去。这样example完全不用关心db连接怎么生成的了。
这就叫依赖注入,实现不是在代码内部创建依赖关系,而是让其作为一个参数传递,这使得我们的程序更容易维护,降低程序代码的耦合度,实现一种松耦合。
这还没完,我们再假设example类里面除了db还要用到其他外部类,我们通过:
我们没完没了的写这么多set?累不累?
4、ok,为了不用每次写这么多行代码,我们又去弄了一个工厂方法:
实例化example时变为:
似乎完美了,但是怎么感觉又回到了上面第一次用工厂方法时的场景?这确实不是一个好的解决方案,所以又提出了一个概念:容器,又叫做IoC容器、DI容器。
我们本来是通过setXXX方法注入各种类,代码很长,方法很多,虽然可以通过一个工厂方法包装,但是还不是那么爽,好吧,我们不用setXXX方法了,这样也就不用工厂方法二次包装了,那么我们还怎么实现依赖注入呢?
5、这里我们引入一个约定:在example类的构造函数里传入一个名为Di $di的参数,如下:
Di就是IoC容器,所谓容器就是存放我们可能会用到的各种类的实例,我们通过$di->set()设置一个名为db的实例,因为是通过回调函数的方式传入的,所以set的时候并不会立即实例化db类,而是当$di->get('db')的时候才会实例化,同样,在设计di类的时候还可以融入单例模式。
这样我们只要在全局范围内申明一个Di类,将所有需要注入的类放到容器里,然后将容器作为构造函数的参数传入到example,即可在example类里面从容器中获取实例。当然也不一定是构造函数,你也可以用一个 setDi(Di $di)的方法来传入Di容器,总之约定是你制定的,你自己清楚就行。
四、laravel中的实际用法
以上我们了解IOC这种设计模式,接下来我们来看看laravel里面的$app的各个类是如何注入进去的。
资料参考:http://www.golaravel.com/laravel/docs/4.2/lifecycle/
翻代码找到入口文件:
1.文件路径:\public\index.php
2.文件路径:\bootstrap\start.php
3.vendor start,文件路径:\paf-vendor\laravel\v4.2.11\vendor\laravel\framework\src\Illuminate\Foundation\start.php
4.文件
\v4.2.11\vendor\laravel\framework\src\Illuminate\Foundation\ProviderRepository.php
\v4.2.11\vendor\laravel\framework\src\Illuminate\Foundation\Application.php
回过头来看看我们的每个provider怎么写的:\v4.2.11\vendor\laravel\framework\src\Illuminate\Cookie\CookieServiceProvider.php
总算回到主题上来了,其实我们就是想研究是怎么注入进去的,关注bindShared方法或者是bind方法。
关键文件:\v4.2.11\vendor\laravel\framework\src\Illuminate\Container\Container.php
我们看看容器里面的对象是怎么调用了:
五、IOC模式的思考和疑问
IOC,是现在很火的设计模式,就像当年的Factory和Singleton模式一样。IOC模式为我们提供了真正的松散耦合,但是松散耦合真的这么酷吗?紧耦合真的一无是处吗?不见得。
首先,使用IOC模式就必然会依赖于一些IOC容器,对于一些要求响应速度的系统而言,IOC的使用必然会降低系统性能(new 的速度肯定比Class.forName块),缓存?忘记它吧,我已经强调响应速度了。再说,IOC跟缓存也没有必然联系呀。
其次,IOC模式的大量使用会降低一些复杂模块的可读性,要知道,如果你不能写出很好的文档(多数人都是如此),那么代码就是你唯一可以与其他人沟通的语言。如果阅读代码的人不懂IOC,他如何理解你那些接口的实现?
第三,IOC容器的大量使用会造成额外的维护成本。尤其是,虽然代码中不存在耦合关系,但是耦合关系都在配置文件中,你在写出松散耦合的代码的同时也必须去写紧耦合的配置文件,对于一个大型系统而言,大量的配置文件的管理本身就必须付出高昂的代价。
最后,我认为真正意义上的松耦合是不存在的――是的,你可以“依赖”接口编程――但是毕竟还是“依赖”了。既然没有绝对的松散耦合,那么我们是否可以考虑在一定范围内使用紧耦合呢?ArrayList和Collection是紧耦合,你是否觉得不便呢,ArrayList和Iterator更是紧耦合,难道Iterator不好用吗?
考虑一下传统的工厂模式吧,它在一定程度上体现了IOC的思想,但是又没有完全的实现IOC。你可以使用工厂模式实现接口编程,但是依赖关系仍然需要在代码中体现。Hibernate是公认的优秀产品,它没有使用IOC,大量的Factory充斥其中,但是Hiberante的质量和升级速度有目共睹。也许你会说,IOC容器提供了很多底层的东西,例如缓存和对象生命周期管理等,以Spring为例,缓存倒是有,可是生命周期管理就不见得,事务管理还是需要客户介入。再说了,这些都是容器提供的,IOC模式并没有要求。之所以谈这些,主要是考虑实现一个带有缓存和“挂钩点”的工厂,这个工厂提供对象的创建和pool&cache管理但是不提供依赖关系管理。
那么IOC这个模式在哪里使用呢,我认为,应该在构件这个级别使用。构件应该是一个封装的很好的模块,它提供独立的、具有实际意义的功能。通过对构件的粒度的设计控制IOC使用的密度。而构件的内部,可以使用传统的工厂模式,也可以什么模式都不用^_^。只要提供清晰准确的接口,并且封装接口在构件内部的实现,那么即使使用public变量都没有关系!
名词:
闭包:http://php.net/manual/zh/functions.anonymous.php
反射:http://php.net/manual/zh/book.reflection.php
资料来源:
http://segmentfault.com/a/1190000002411255
http://segmentfault.com/a/1190000002424023
http://www.cnblogs.com/DebugLZQ/archive/2013/06/05/3107957.html
http://blog.youkuaiyun.com/realghost/article/details/35212285