依赖反转 和 依赖注入 (PHP)

读了文章 《深刻探討依賴注入 》作下笔记和总结php

文章主要说了这样一个思想进阶过程:函数

传统实现 —— Interface(依赖反转) —— 工廠模式 —— Constructor Injection(依赖注入) —— Method Injectionpost

 

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以后就能够写成: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

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个目标:ip

  1. 容易維護 
  2. 容易新增功能 
  3. 容易重複使用 
  4. 容易上Git,不易與其余人衝突 
  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,使程序更加精簡

------------------------------------------------------------------

原文中的一段精髓,认为归纳的很好:

  • 當A class 去 new B class,A 就是高階模組,B就是低階模組。
  • 高階模組不應該去 new 低階模組,也就是 class,而應該由高階模組定義 interface。

  • 高階模組只依賴本身定義的 interface,而低階模組也只依賴 (實踐) 高階模組所定義的 interface。

 简单说,interface 就是一个 标准 上下游环节都按照该标准完成各自的工做。

相关文章
相关标签/搜索