IOC簡介
IOC(Inversion of Control),即控制反轉,它是一種軟件架構思想,強調將類之間的依賴關係處理,核心思想就是將被依賴類交由第三方控制,即依賴類不會在內部直接new被依賴類來完成依賴管理。目的是爲了實現鬆耦合的軟件架構。
舉例,我們常通過正轉來管理兩個依賴的類,但這樣耦合性太高,假設B依賴A,則代碼如下:
<?php
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兩個依賴類完全獨立了,進行了組件解耦,實現了鬆耦合。
IOC的實現方法
早在2004年,Martin Fowler就提出了“哪些方面的控制被反轉了?”這個問題。他總結出是依賴對象的獲得被反轉了,因爲大多數應用程序都是由兩個或是更多的類通過彼此的合作來實現企業邏輯,這使得每個對象都需要獲取與其合作的對象(也就是它所依賴的對象)的引用。如果這個獲取過程要靠自身實現,那麼這將導致代碼高度耦合並且難以維護和調試。
經過發展,實現IOC主要有三種設計模式:抽象工廠模式,服務定位器模式,依賴注入(DI)模式。
目前DI被廣泛用於實現IOC,所以很多資料幾乎把二者等同了,但還是要區分一下的。這裏主要介紹DI,另外兩個請看OOP設計模式大全。
既然是依賴注入模式,主要說的就是注入方式了,共三種:
1:接口注入
接口注入強制被注入者實現接口類定義的方法,具有過強的侵入性,所以幾乎不用了。接口注入要定義一個接口和注入的方法,例子如下:
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();
2:setter方法注入
其實就是命名一個特別的方法(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();
3:構造函數注入
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注入和構造函數注入,各有千秋。
IOC容器
如上面的代碼,在小型項目中,直接$a= new A(); $b=new B($a); ...
即在類外部創建類的實例,並將其引用傳遞給依賴者。但在大型項目(如Laravel,Spring)中,依賴的類非常多,如果還手動創建,效率非常低。這個時候就需要IOC容器來參與了,他的作用:
- 動態創建,註冊依賴的對象;
- 管理對象生命週期;
- 映射依賴關係。
IOC容器是一個依賴管理工具,實現了管理依賴,自動注入功能,實現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 學習筆記 —— 神奇的服務容器