iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 6
1
Software Development

你終究都要學設計模式的,那為什麼不一開始就學呢?系列 第 6

Day6. 依賴反轉原則

本文同步更新於blog

Dependency Inversion Principle (DIP)

High-level modules should not depend on low-level modules. Both should depend on abstractions

高階模組不應該依賴低階模組。它們都應該依賴抽象。

Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions.

抽象不應該依賴細節。細節應該依賴抽象。


目的是把高階模組對低階模組的依賴解耦,
改為高階模組依賴定義出的抽象介面
而由低階模組去實現介面

其中的依賴關係被顛倒,使得低階模組依賴於高階的抽象介面。


Def. 發電:泛指從其它種類的能源轉換為電力的過程

最初我們有一個電力系統,發電系統採用火力發電
之後再由輸電系統,配給各個用戶。

今天我們想要抽換發電系統,改為風力發電
卻發現原本的電力系統強耦合於火力發電,不易抽換。

透過定義出抽象的發電介面,我們可以讓電力系統依賴於發電介面,
並讓火力發電、風力發電去實作發電介面。

因為依賴抽象化,未來就更容易抽換發電系統了。


以下提供範例程式碼:

情境:目前電力系統採用火力發電

  • 火力發電
<?php

namespace App\SOLID\DIP\PowerSystems;

class ThermalPower
{
    public function generatePower()
    {
        return '電力';
    }
}

  • 電力系統
<?php

namespace App\SOLID\DIP\PowerSystems;

use App\SOLID\DIP\PowerSystems\ThermalPower;

class Program
{
    public function getPower()
    {
        $thermalPower = new ThermalPower();
        return $thermalPower->generatePower();
    }
}

隨著科技發展,現在我們想要改成用風力發電來取代火力發電,
卻發現原本的程式,強耦合在ThermalPower
(依賴了具體的類別)

我們決定定義一個抽象的介面,
讓程式依賴在介面,並由各個發電方式實作介面。
改變彼此的依賴關係。


需求一:定義抽象介面,改變電力系統與火力發電的依賴關係

  • 首先定義發電介面
<?php

namespace App\SOLID\DIP\PowerSystems\Contracts;

interface PowerGeneratable
{
    public function generatePower();
}

  • 接著讓火力發電實作發電介面
<?php

namespace App\SOLID\DIP\PowerSystems;

use App\SOLID\DIP\PowerSystems\Contracts\PowerGeneratable;

class ThermalPower implements PowerGeneratable
{
    public function generatePower()
    {
        return '電力';
    }
}

  • 最後改寫原本的電力系統
<?php

namespace App\SOLID\DIP\PowerSystems;

use App\SOLID\DIP\PowerSystems\Contracts\PowerGeneratable;

class Program
{
    public function getPower(PowerGeneratable $powerGeneration)
    {
        return $powerGeneration->generatePower();
    }
}

(註:現在要用什麼樣的發電方式,會交由客戶端決定)

  • 客戶端使用火力發電(提供測試程式碼,供參考)
<?php

namespace Tests\Feature\SOLID\DIP\PowerSystems;

use PHPUnit\Framework\TestCase;
use App\SOLID\DIP\PowerSystems\Program;
use App\SOLID\DIP\PowerSystems\ThermalPower;

class ProgramTest extends TestCase
{
    /**
     * @var Program
     */
    protected $sut;

    protected function setUp(): void
    {
        $this->sut = new Program();
    }

    public function testGetPowerByThermalPower()
    {
        $expected = '電力';
        $powerGeneration = new ThermalPower();
        $actual = $this->sut->getPower($powerGeneration);
        $this->assertEquals($expected, $actual);
    }
}


需求二:新增風力發電

  • 實作風力發電
<?php

namespace App\SOLID\DIP\PowerSystems;

use App\SOLID\DIP\PowerSystems\Contracts\PowerGeneratable;

class WindPower implements PowerGeneratable
{
    public function generatePower()
    {
        return '電力';
    }
}

  • 客戶端使用風力發電(提供測試程式碼,供參考)
<?php

namespace Tests\Feature\SOLID\DIP\PowerSystems;

use PHPUnit\Framework\TestCase;
use App\SOLID\DIP\PowerSystems\Program;
use App\SOLID\DIP\PowerSystems\ThermalPower;
use App\SOLID\DIP\PowerSystems\WindPower;

class ProgramTest extends TestCase
{
    /**
     * @var Program
     */
    protected $sut;

    protected function setUp(): void
    {
        $this->sut = new Program();
    }

    public function testGetPowerByThermalPower()
    {
        $expected = '電力';
        $powerGeneration = new ThermalPower();
        $actual = $this->sut->getPower($powerGeneration);
        $this->assertEquals($expected, $actual);
    }

    public function testGetPowerByWindPower()
    {
        $expected = '電力';
        $powerGeneration = new WindPower();
        $actual = $this->sut->getPower($powerGeneration);
        $this->assertEquals($expected, $actual);
    }
}


最後附上類別圖比較:

  • 依賴具體的火力發電
    https://ithelp.ithome.com.tw/upload/images/20200921/20111630DMoQlpHYB5.png

  • 依賴抽象的發電介面,並由各個發現方式實作介面
    (可以觀察火力發電與風力發電的依賴方向,是不是反轉了呢)
    https://ithelp.ithome.com.tw/upload/images/20200921/20111630xA0NdMwok0.png

ʕ •ᴥ•ʔ:重讀一次DIP發現重點在於,如何定義出足夠抽象的介面


上一篇
Day5. 介面隔離原則
下一篇
Day7. UML類別圖說明
系列文
你終究都要學設計模式的,那為什麼不一開始就學呢?57
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言