讀了文章 《深刻探討依賴注入 》作下筆記和總結php
文章主要說了這樣一個思想進階過程:函數
傳統實現 —— Interface(依賴反轉) —— 工廠模式 —— Constructor Injection(依賴注入) —— Method Injectionpost
class ShippingService { /** * @param string $companyName * @param int $weight * @return int * @throws Exception */ public function calculateFee($companyName, $weight) { if ($companyName == 'BlackCat') { $blackCat = new BlackCat(); return $blackCat->calculateFee($weight); } elseif ($companyName == 'Hsinchu') { $hsinchu = new Hsinchu(); return $hsinchu->culateFee($weight/1000);//$weight的單位是 g 而這裏須要 kg } elseif ($companyName == 'PostOffice') { $postOffice = new PostOffice(); return $postOffice->getFee($weight); } else { throw new Exception('No company exception'); } } }
文章使用了算郵費的例子,這樣 ShippingService 就要依賴三種郵費計算 class ,文章中的例子仍是比較整齊的,而實際狀況可能更糟:單元測試
由於三種郵費計算 class 可能不是一我的寫的 函數名可能不同 參數個數和類型也可能不同。。。就像上面的例子(對原文的例子稍有修改)測試
//定義接口 interface LogisticsInterface { /** * @param int $weight * @return int */ public function calculateFee($weight); } //實現接口 class BlackCat implements LogisticsInterface { /** * @param int $weight * @return int */ public function calculateFee($weight) { return 100 * $weight * 10; } }
使用了Interface以後就能夠寫成:this
class ShippingService { public function calculateFee($companyName, $weight) { switch ($companyName) { case 'BlackCat': $logistics = new BlackCat(); case 'Hsinchu': $logistics = new Hsinchu(); case 'PostOffice': $logistics = new PostOffice(); default: throw new Exception('No company exception'); } //有了統一的接口 就能夠統一調用 return $logistics->calculateFee($weight); } }
這樣三種郵費類依賴Interface對外提供相同的接口 而ShippingService也依賴於Interface不用擔憂郵費類發生變化 從而實現了依賴反轉spa
可是 ShippingService 依然要 new 三種郵費出來,依賴於Interface ,郵費類雖然不會變化 可是可能會 去掉或增長 code
class LogisticsFactory { public static function create(string $companyName) { switch ($companyName) { case 'BlackCat': return new BlackCat(); case 'Hsinchu': return new Hsinchu(); case 'PostOffice': return new PostOffice(); default: throw new Exception('No company exception'); } } } class ShippingService { public function calculateFee($companyName, $weight) { $logistics = LogisticsFactory::create($companyName); return $logistics->calculateFee($weight); } }
使用工廠模式後 業務層(ShippingService)已經徹底和那三個討厭的傢伙說拜拜了 今後徹底實現了依賴反轉接口
而即使是這樣 ShippingService 也仍是同時依賴了工廠類和 interface ,同時也像文章說的還有單元測試的問題, 即程序的5個目標:ip
中的第5條尚未實現;何況加一層工廠類顯得有點多餘(至少在本例中)
在實際開發中狀況是這樣的:
當兩我的合做開發,一我的負責ShippingService 另外一我的負責三種郵費計算類(固然工廠類也應該由他來實現);當 ShippingService 開發完成以後 而郵費計算這邊尚未完成,這時候要對ShippingService作單元測試,你能夠根據 interface 模擬一個郵費計算類,可是你不知道工廠類作了什麼,因此就沒有辦法徹底模擬出所依賴模塊的行爲。
-------------------------------------------------------
(其實,因爲 PHP 的 interface 只定義了輸入 沒有限制輸出,致使 interface 的行爲仍是有不肯定性)
class ShippingService { /** @var LogisticsInterface */ private $logistics; /** * ShippingService constructor. * @param LogisticsInterface $logistics */ public function __construct(LogisticsInterface $logistics) { $this->logistics = $logistics; } /** * @param int $weight * @return int */ public function calculateFee($weight) { return $this->logistics->calculateFee($weight); } }
將第三方依賴(郵費計算類)經過參數傳入,並指定類型;摒棄了工廠類 實現了最大程度的解耦(只依賴於 interface)
class ShippingService { /** * @param LogisticsInterface $logistics * @param int $weight * @return int */ public function calculateFee(LogisticsInterface $logistics, $weight) { return $logistics->calculateFee($weight); } }
這一步主要是解決了原文所說的:「只要 class 要實現依賴注入,惟一的管道就是 constructor injection,如有些相依物件只有單一 method 使用一次,也必須使用 constructor injection,這將導致最後 constructor 的參數爆炸而難以維護」的問題;而且沒必要再使用 constructor 與 field,使程序更加精簡
------------------------------------------------------------------
原文中的一段精髓,認爲歸納的很好:
- 當A class 去
new
B class,A 就是高階模組,B就是低階模組。高階模組不應該去
new
低階模組,也就是 class,而應該由高階模組定義 interface。高階模組只依賴本身定義的 interface,而低階模組也只依賴 (實踐) 高階模組所定義的 interface。
簡單說,interface 就是一個 標準 上下游環節都按照該標準完成各自的工做。