读了文章 《 》做下笔记和总结
文章主要说了这样一个思想进阶过程:
传统实现 —— Interface(依赖反转) —— 工廠模式 —— Constructor Injection(依赖注入) —— Method Injection
1.传统实现
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 可能不是一个人写的 函数名可能不一样 参数个数和类型也可能不一样。。。就像上面的例子(对原文的例子稍有修改)
2.Interface(依赖反转)
//定义接口
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之后就可以写成:
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不用担心邮费类发生变化 从而实现了依赖反转
但是 ShippingService 依然要 new 三种邮费出来,依赖于Interface ,邮费类虽然不会变化 但是可能会 去掉或增加
3.工廠模式
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个目标:
中的第5条还没有实现;况且加一层工厂类显得有点多余(至少在本例中)
在实际开发中情况是这样的:
当两个人合作开发,一个人负责ShippingService 另一个人负责三种邮费计算类(当然工厂类也应该由他来实现);当 ShippingService 开发完成之后 而邮费计算这边还没有完成,这时候要对ShippingService做单元测试,你可以根据 interface 模拟一个邮费计算类,但是你不知道工厂类做了什么,所以就没有办法完全模拟出所依赖模块的行为。
-------------------------------------------------------
(其实,由于 PHP 的 interface 只定义了输入 没有输出,导致 interface 的行为还是有不确定性)
4.Constructor Injection(依赖注入)
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)
5.Method Injection
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,使程序更加精簡
------------------------------------------------------------------
原文中的一段精髓,认为概括的很好:
简单说,interface 就是一个 标准 上下游环节都按照该标准完成各自的工作。