用過 laravel 框架的人一定都聽過控制反轉和依賴注入這個概念,對於很多初學者來說,對這兩個概念很難理解,當初我也不例外,現在我就將我自己的理解分享出來,如果有什麼不正確的地方還請指出。這裡我不僅會給出這兩個概念的見解,還會給出依賴倒置和 IOC容器 的見解。
一.依賴倒轉。
開始之前,我們先來看一下什麼是依賴倒轉原則。依賴倒轉原則書本上給出的說明是:
- 高層模組不應該依賴於低層模組。它們都應該依賴抽象。
- 抽象不應該依賴於細節,細節應該依賴於抽象。
在開始之前,我們先來理解幾個概念,在這個過程中我會給一個老闆,Hr,員工的例子。
-
1,什麼是依賴?
老闆需要做一件事情,然後這件事情能夠完成的人是他的員工,這個時候老闆就依賴員工。在程式設計中我理解的是:一個段程式碼的實現需要另外一段程式碼的參與,那麼這就是一種依賴的關係。
- 2.什麼是高層模組,什麼是低層模組?
在這個例子中,老闆就是高層,員工就是低層。在程式設計中我理解的是:實現低層操作的程式碼屬於低層程式碼,比如說檔案操作、資料庫操作;通過引用底層程式碼來實現一些業務邏輯的程式碼就是高層程式碼。
為什麼說高層模組不應該依賴低層模組呢?下面文字也許會讓你明白。
老闆的公司有一個新專案,需要招一個人去完成它,老闆去招聘的時候不能直接隨便找一個人就好了吧,他需要有一個招人的標準(能夠完成專案能力的標準),這個時候老闆依賴的是這個標準,只有應聘的人滿足這個標準(應聘的人也依賴這個標準),老闆才可能會招聘他。這個招聘的過程中,老闆(高層模組)不依賴員工(低層模組),而是依賴能夠完成專案的能力標準(高層模組依賴抽象),而員工也只有滿足這個標準(低層模組依賴抽象),老闆才可能招聘他。
上邊的例子就說明了為什麼高層模組不應該依賴底層模組,它們都應該依賴抽象。
- 3.什麼是抽象?
抽象在oop中指的是:具有一類相同特徵事物的集合。這個招聘過程中,這個招聘標準扮演抽象的角色,這個標準不需要完成老闆的專案,只需要來約束應聘的員工具有這樣的能力。
為什麼說抽象不應該依賴於細節而細節應該依賴抽象?下面文字也許會讓你明白。
老闆為了員工能完成專案所規定的一個能力標準,這個標準他不需要依賴某個員工(標準提供在這裡,如果員工技能滿足就上,不滿足就換人,所以抽象不依賴細節),員工的技能不滿足這個標準,那麼換另外一個人就好了。但是員工就不一樣了,他的技能必須必須滿足這個標準(這裡的技能就是具體的實現細節,標準就是抽象,細節依賴抽象)後才有可能進入這家公司。
上邊的例子說明了為什麼抽象不應該依賴於細節,而細節應該依賴於抽象。
下面用程式碼來實現這個關係:
<?php
class Boos{
//領導依賴員工
private $staff;
public function setStaff(){
//來應聘的員工A
$staff = new StaffA();
//老闆判斷是否滿足技能
if($staff instanceof Standard){
$this->staff = $staff;
}else{
throw new Exception('該員工沒有我需要的工作能力');
}
}
public function task(){
$this->staff->work();
}
}
//招聘所設定的標準
interface Standard{
public function work();
}
//員工需要依賴的標準
class StaffA implements Standard{
public function work(){
echo '僱員A有能力能夠完成老闆指定的工作';
}
}
class StaffB implements Standard{
public function work(){
echo '僱員B有能力能夠完成老闆指定的工作';
}
}
看了上面的例子,我想應該對依賴倒轉有一定的瞭解了,我再總結一下:
上層呼叫模組和下層被呼叫模組都不應該彼此直接依賴對方,而是應該依賴一個抽象的規則(介面或者時抽象類),專業一點的說法就時程式設計要針對介面程式設計,不要針對實現來程式設計。這樣寫出的程式碼可維護,可擴充,靈活性好。
二.控制反轉( IOC )、 IOC 容器和依賴注入( DI )。
控制反轉(Inversion of Control,縮寫為 IOC),書本上給出的解釋是:
- 模組間的依賴關係從程式內部提到外部來例項化管理稱之為控制反轉,這個例項化的過程就叫做依賴注入。
在理解這個解釋前我們先理解下下面幾個詞:
-
程式內部。
程式內部就是上層模組。
- 程式外部。
程式外部就是既不是上層模組,也不是下層模組的第三方模組或者是上下文。
我們繼續看上邊的老闆招人的這段程式碼:
public function setStaff(){
//來應聘的員工A
$staff = new StaffA();
//老闆判斷是否滿足技能
if($staff instanceof Standard){
$this->staff = $staff;
}else{
throw new Exception('該員工沒有我需要的工作能力');
}
}
發現什麼沒有,這個員工是老闆親自招的(員工類的例項化在老闆類中),想想,如果老闆的公司做大了,他還有這麼多的時間去親自招人嗎?(如果老闆類需要換一個員工類,每次都需要去改變老闆類,違反了 開放-關閉 原則),老闆親自招人的這種做法就叫做控制正轉(控制正傳這個名字是我瞎想的,差不多就是這個意思)。
人都是聰明的,這個時候老闆會去招一個 hr ,然後招人的時候老闆只需要給 hr 說一下我現在需要招人, hr 就會把人找好然後交給老闆。這個時候招人的控制權老闆交給了 hr。這個就是控制反轉,而這個 hr 可以把他當作一個 IOC 容器, hr 這個招人的動作可以理解為就是依賴注入。
我們來用程式碼實現控制反轉的招人方式:
<?php
class Boos{
//領導依賴員工
private $staff;
//現在老闆只需要接受 hr 招聘就好,將控制權交給 hr
//以設定方法來實現依賴注入
public function setStaff(Standard $staff){
$this->staff = $staff;
}
public function task(){
$this->staff->work();
}
}
//招聘所設定的標準
interface Standard{
public function work();
}
//員工需要依賴的標準
class StaffA implements Standard{
public function work(){
echo '僱員A有能力能夠完成老闆指定的工作';
}
}
class StaffB implements Standard{
public function work(){
echo '僱員B有能力能夠完成老闆指定的工作';
}
}
//ioc容器
class Hr{
public function getStagff(){
return new StaffB();
}
}
//公司老闆
$boos = new Boos();
//老闆招的hr
$hr = new Hr();
$staff = $hr->getStagff();
//hr把招到的人給老闆(控制反轉和依賴注入)
$boos->setStaff($staff);
//老闆讓他工作了
$boos->task();
上面的程式碼簡單的實現了依賴反轉,控制反轉,ioc容器和依賴注入。
看了上邊簡單易懂的程式碼後,我將上邊的程式碼重寫一下,這次重寫仿照 laravel 的IOC容器和依賴注入:
<?php
class Boos{
//領導依賴員工
private $staff;
//老闆只需要告訴外部我需要什麼樣的人就好了,其它什麼都不管,具體什麼樣的人交給外部處理。
//用構造方法方式實現依賴注入
public function __construct(Standard $staff){
$this->staff = $staff;
}
public function task(){
$this->staff->work();
}
}
//招聘所設定的標準
interface Standard{
public function work();
}
//員工需要依賴的標準
class StaffA implements Standard{
public function work(){
echo '僱員A有能力能夠完成老闆指定的工作';
}
}
class StaffB implements Standard{
public function work(){
echo '僱員B有能力能夠完成老闆指定的工作';
}
}
class Hr{
private $binds = [];
//接受不同員工的簡歷,並存起來
public function bind($contract,$concrete){
$this->binds[$contract] = $concrete;
}
//詢問老闆選人的標準由哪些,並且從滿足的簡歷中篩選人
private function methodBindParams($className){
$reflect = new reflect($className,'__construct');
return $reflect->bindParamsToMethod();
}
//將選好的工作人員交給老闆
public function make($className){
$methodBindParams = $this->methodBindParams($className);
$reflect = new reflect($className,'__construct');
return $reflect->make($this->binds,$methodBindParams);
}
}
class reflect{
private $className;
private $methodName;
public function __construct($className,$methodName){
$this->className = $className;
$this->methodName = $methodName;
}
//繫結引數到方法
public function bindParamsToMethod(){
$params = [];
$method = new ReflectionMethod($this->className,$this->methodName);
foreach ($method->getParameters() as $param) {
$params[] = [$param->name,$param->getClass()->name];
}
return [$this->className=> $params];
}
public function make($bind,$methodBindParams){
$args = [];
foreach ($methodBindParams as $className => $params) {
foreach ($params as $param) {
list($paramName,$paramType) = $param;
$paramName = new $bind[$paramType]();
array_push($args, $paramName);
}
}
$reflectionClass = new ReflectionClass($this->className);
return $reflectionClass->newInstanceArgs($args);
}
}
$hr = new Hr();
//老闆如果需要換工作人員,只需要繫結其它的工作人員即可。
$staff = $hr->bind('Standard','StaffA');
$boos = $hr->make('Boos');
$boos->task();
以上就是我的理解,如果由什麼不正確的地方,還請大家多多指出。