依賴反轉 和 依賴注入 (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 就是一個 標準 上下游環節都按照該標準完成各自的工做。

相關文章
相關標籤/搜索