IOC(Inversion of Control),即控制反转,它是一种软件架构思想,强调将类之间的依赖关系处理,核心思想就是将被依赖类交由第三方控制,即依赖类不会在内部直接new被依赖类来完成依赖管理。目的是为了实现松耦合的软件架构。
举例,我们常通过正转来管理两个依赖的类,但这样耦合性太高,假设B依赖A,则代码如下:
class A
{
public $data;
public function eat()
{
return 'I\'m a dog,i want to eat pork';
}
}
class B
{
public function eat()
{
$a = new A();
echo $a->eat();
}
}
$b=new B($a);
$b->eat();
可以看到,B是高层次类,A是低层次(具现)类,违反OOP五大原则依赖倒置(DIP)原则,如果B不依赖A,而依赖C,就需要修改B代码,这个时候违反了开放封闭原则。
所以说:通过IOC可以解决这类问题,将上面代码改造一下,如下:
interface D
{
public function eat();
}
class A implements D
{
public $data;
public function eat()
{
return 'I\'m a dog,i want to eat pork';
}
}
class B
{
protected $animal;
public function __construct(D $d)
{
$this->animal = $d;
}
public function eat()
{
echo $this->animal->eat();
}
}
$a= new A();
$b=new B($a);
$b->eat();
可以看到,改造后的代码,将A的实例化拿到B的外面来做,使B依赖接口类D,这样实现DIP,如果突然增加一个C,也无需改动B的代码。
其实这样的改造使用的DI设计模式。
总结:IOC是一种编程思想,它是把查找和实例化所依赖的类交给了第三方处理,这样通过IOC,将B,A两个依赖类完全独立了,进行了组件解耦,实现了松耦合。
早在2004年,Martin Fowler就提出了“哪些方面的控制被反转了?”这个问题。他总结出是依赖对象的获得被反转了,因为大多数应用程序都是由两个或是更多的类通过彼此的合作来实现企业逻辑,这使得每个对象都需要获取与其合作的对象(也就是它所依赖的对象)的引用。如果这个获取过程要靠自身实现,那么这将导致代码高度耦合并且难以维护和调试。
经过发展,实现IOC主要有三种设计模式:抽象工厂模式,服务定位器模式,依赖注入(DI)模式。
目前DI被广泛用于实现IOC,所以很多资料几乎把二者等同了,但还是要区分一下的。这里主要介绍DI,另外两个请看OOP设计模式大全。
既然是依赖注入模式,主要说的就是注入方式了,共三种:
接口注入强制被注入者实现接口类定义的方法,具有过强的侵入性,所以几乎不用了。接口注入要定义一个接口和注入的方法,例子如下:
interface D
{
public function eat();
}
interface E
{
public function setDependence();
}
class A implements D
{
public $data;
public function eat()
{
return 'I\'m a dog,i want to eat pork';
}
}
class B implements E
{
protected $animal;
public function setDependence(D $d)
{
$this->animal = $d;
}
public function eat()
{
echo $this->animal->eat();
}
}
$a= new A();
$b=new B($a);
$b->eat();
其实就是命名一个特别的方法(setX),来注入。
interface D
{
public function eat();
}
class A implements D
{
public $data;
public function eat()
{
return 'I\'m a dog,i want to eat pork';
}
}
class C implements D
{
public $data;
public function eat()
{
return 'I\'m a cat,i want to eat fish';
}
}
class B
{
protected $animal;
public function setA(A $a)
{
$this->animal = $a;
}
public function setC(C $c)
{
$this->animal = $c;
}
public function eat()
{
echo $this->animal->eat();
}
}
$a= new A();
//$c = new C();
$b=new B();
$b->setA($a);
$b->eat();
interface D
{
public function eat();
}
class A implements D
{
public $data;
public function eat()
{
return 'I\'m a dog,i want to eat pork';
}
}
class B
{
protected $animal;
public function __construct(D $d)
{
$this->animal = $d;
}
public function eat()
{
echo $this->animal->eat();
}
}
$a= new A();
$b=new B($a);
$b->eat();
setter注入和构造函数注入,各有千秋。
如上面的代码,在小型项目中,直接$a= new A(); $b=new B($a); ...
即在类外部创建类的实例,并将其引用传递给依赖者。但在大型项目(如Laravel,Spring)中,依赖的类非常多,如果还手动创建,效率非常低。这个时候就需要IOC容器来参与了,他的作用:
class Container
{
protected $binds = [];
protected $instances = [];
//映射依赖关系
public function bind($abstract, $concrete)
{
if ($concrete instanceof Closure) {
$this->binds[$abstract] = $concrete;
} else {
$this->instances[$abstract] = $concrete;
}
}
//动态创建,注册依赖的对象
public function make($abstract, $parameters = [])
{
//管理对象生命周期
if (isset($this->instances[$abstract])) {
return $this->instances[$abstract];
}
array_unshift($parameters, $this);//
return call_user_func_array($this->binds[$abstract], $parameters);
}
}
使用,假设这样的场景,有一个自动驾驶软件,需要灵幻切换不同的车型,如宝马,吉利,奥迪等,实现如下:
//自动驾驶类,高层次类,相比具体车型的类更抽象
class AutoSoft
{
protected $car;
public function __construct(Car $car)
{
$this->car = $car;
}
public function autoRun()
{
$this->car->run();
}
public function autoStop()
{
$this->car->stop();
}
}
//接口类
interface Car
{
public function run();
public function stop();
}
//宝马类
class BM implements Car
{
public function run()
{
echo '宝马车启动了';
}
public function stop()
{
echo '宝马车停止了';
}
}
//奥迪类
class AD implements Car
{
public function run()
{
echo '奥迪车启动了';
}
public function stop()
{
echo '奥迪车停止了';
}
}
使用
//实例化容器
$container = new Container();
//向容器添加自动驾驶软件类的实例
$container->bind('autosoft', function($container, $moduleName) {
return new AutoSoft($container->make($moduleName));
});
//向容器添加宝马,奥迪类的实例
$container->bind('bm', function($container) {
return new BM();
});
$container->bind('ad', function($container) {
return new AD();
});
//自动注入
$auto_soft = $container->make('autosoft', ['bm']);//注入宝马车驾驶
//$auto_soft = $container->make('autosoft', ['ad']);//切换到奥迪车
$auto_soft->autoRun();
$auto_soft->autoStop();
【1】 IOC及其应用;
【2】 深入理解DIP、IoC、DI以及IoC容器;
【3】 什么是IOC和什么是AOP;
【4】 依赖注入(DI)和依赖查找(DL);
【5】 laravel 学习笔记 —— 神奇的服务容器