一.控制反转,依赖注入简介
控制反转和依赖注入说的实际上是同一个东西,它们是一种设计模式,这种设计模式用来减少程序间的耦合
什么是控制反转
Inversion of Control 简写IOC ,他不是一种技术,从容器的角度在描述来看:容器控制应用程序,由容器反向的向应用程序注入应用程序所需要的外部资源
在这里需要理解几个词:谁控制谁,控制什么,为何是反转(有反转就应该有正转了),哪些方面反转了?
1.1谁控制谁,控制什么
传统程序设计,我们直接在对象内部通过new进行创建实例对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对 象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等);
1.2为何是反转,哪些方面反转了
有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转,考虑一下常规情况下的应用程序,如果要在A里面使用C,会怎么做呢?当然是直接去创建C的对象,也就是说,是在A类中主动去获取所需要的外部资源C($c = new C();)一句话来解释:正转就是按照普通的在类中直接创建对象实例,如 $c = new C(),这种情况被称为正转的。那么什么是反转呢?就是A类不再主动去获取C,而是被动等待,等待IoC/DI的容器获取一个C的实例,然后反向的注入到A类中,为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了
1.3 控制反转能做些什么以及作用?
传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;但是有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是 松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。
注:IoC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来创建并注入它所需要的资源了。
IoC很好的体现了面向对象设计法则之一—— 好莱坞法则:“别找我们,我们找你”;即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。
1.4 控制反转分类
依赖注入(Dependency Injection 简写DI) 这个应用比较广泛
依赖查找(Dependency Lookup 检查DL)
2.什么是依赖注入
Dependency Injection 简写DI,他是实现控制反转思想的的技术方式,从应用程序的角度来描述:应用程序依赖容器创建并注入它所需要的外部资源
为什么使用依赖注入呢
有效的分离了对象和它所需要的外部资源,使得它们松散耦合,有利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活
组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。也需要理解几个词:谁依赖谁,为什么需要依赖,谁注入谁,注入了什么?
2.1谁依赖于谁
当然是应用程序依赖于IoC容器
2.2为什么需要依赖
应用程序需要IoC容器来提供对象需要的外部资源
2.3谁注入谁
很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象
2.4 注入了什么
就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)
二.案例演示
案例1
没有IoC/DI的时候,常规的A类使用C类的示例
<?php
/**
* 没有IoC/DI的时候,常规的A类使用C类的示例
*/
/**
* Class c
*/
class c
{
public function say()
{
echo 'hello';
}
}
/**
* Class a
*/
class a
{
private $c;
public function __construct()
{
$this->c = new C(); // 实例化创建C类
}
public function sayC()
{
echo $this->c->say(); // 调用C类中的方法
}
}
$a = new a();
$a->sayC();
当有了IoC/DI的容器后,a类依赖c实例注入的示例
<?php
/**
* 当有了IoC/DI的容器后,a类依赖c实例注入的示例
*/
/**
* Class c
*/
class c {
public function say()
{
echo 'hello';
}
}
/**
* Class a
*/
class a {
private $c;
public function setC(C $c) {
$this->c = $c; // 实例化创建C类
}
public function sayC(){
echo $this->c->say(); // 调用C类中的方法
}
}
$c = new C();
$a = new a();
$a->setC($c);
$a->sayC();
其实IoC/DI对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC/DI容器来创建并注入它所需要的资源了
案例2
<?php
class A
{
public $b ;
public $c ;
public function A()
{
//TODO
}
public function Method()
{
$this ->b= new B();
$this ->c= new C();
$this ->b->Method();
$this ->c->Method();
//TODO
}
}
class B
{
public function B()
{
//TODO
}
public function Method()
{
//TODO
echo 'b' ;
}
}
class C
{
public function C()
{
//TODO
}
public function Method()
{
//TODO
echo 'c' ;
}
}
$a = new A();
$a ->Method();
?>
上面代码可以看出:A类 依赖B类和C类,也就是说,如果今后开发过程中,要对B类或者C类修改,一旦涉及函数改名,函数参数数量变动,甚至整个类结构的调整,我们也要对A类做出相应的调整,A类的独立性丧失了,这在开发过程中是很不方便的,也就是我们说的“牵一发动全身”,如果两个类是两个人分别写的,矛盾就产生了,万一真的要改动B类和C类,有没有办法,可以不去改动或者尽量少改动A类的代码呢?这里要用到 控制反转(高层模块不应该依赖于底层模块,两个都应该依赖抽象)
方法1 构造器注入(不推荐用)
class A
{
public $b ;
public $c ;
public function A( $b , $c )
{
$this ->b= $b ;
$this ->c= $c ;
}
public function Method()
{
$this ->b->Method();
$this ->c->Method();
}
}
客户端:
$a = new A(new B(), new C());
$a ->Method();
A类的构造器依赖B类和C类,通过构造器的参数传入,至少实现了一点,就是B类对象b和C类对象c的创建都移至了A类外,所以一旦B类和C类发生改动,A类无需做修改,只要在client类里改就可以了,假如有一天,我们需要扩充B类,做两个B类的子类
class B
{
public function B()
{
//TODO
}
public function Method()
{
//TODO
echo 'b' ;
}
}
class B1 extends B
{
public function B1()
{
//TODO
}
public function Method()
{
echo 'b1' ;
}
}
class B2 extends B
{
public function B2()
{
//TODO
}
public function Method()
{
echo 'b2' ;
}
}
也很简单,客户端写法:
$a = new A( new B2(), new C());
$a ->Method();
所以A类是不用关心B类到底有哪些个子类的,只要在客户端类关心就可以了
方法2 工厂模式注入(推荐使用)
class Factory
{
public function Factory()
{
//TODO
}
public function create( $s )
{
switch ( $s )
{
case 'B' :
{
return new B();
break ;
}
case 'C' :
{
return new C();
break ;
}
default :
{
return null;
break ;
}
}
}
}
A类代码改为:
class A
{
public $b ;
public $c ;
public function A()
{
//TODO
}
public function Method()
{
$f = new Factory();
$this ->b= $f ->create( 'B' );
$this ->c= $f ->create( 'C' );
$this ->b->Method();
$this ->c->Method();
//TODO
}
}
其实已经解耦了一小部分,至少如果B类和C类的构造函数要是发生变化,比如修改函数参数等,我们只需要改Factory类就可以了
抽象不应该依赖于细节,细节应该依赖于抽象
把B类和C类中的方法再抽象出来,做一个接口
interface IMethod
{
public function Method();
}
这样,A类中的c变量就不再是一个具体的变量了,而是一个抽象类型的变量,不到运行那一刻,不知道他们的Method方式是怎么实现的
class B implements IMethod
{
public function B()
{
//TODO
}
public function Method()
{
//TODO
echo 'b' ;
}
}
class C implements IMethod
{
public function C()
{
//TODO
}
public function Method()
{
//TODO
echo 'c' ;
}
}
总结
1.我们把A类中的B类对象和C类对象的创建移至A类外
2.原本A类依赖B类和C类,现在变成了A依赖Factory,Factory依赖B和C