读了文章 《深刻探討依賴注入 》作下笔记和总结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 就是一个 标准 上下游环节都按照该标准完成各自的工做。