iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 8
0
Software Development

PHP 大師之路 - 開源的技術淬練系列 第 8

Day 8 - PHP 設計模式:依賴注入 (Dependency Injection)

依賴注入也是最常見的模式之一。只要物件之間存在依賴關係,即可抽離相關的程式碼改用注入的方法,把物件注入依賴該物件才能運行的物件中。

在 Day 7 的範例原始碼,我們有一支名為 RedisCache 的類別,它的程式碼是這樣的:

依賴 (Dependency)

範例:/day-7/example-1/CacheRedis.php

class CacheRedis
{
    protected $db;

    public function __construct($setting)
    {
        $host = $setting['host'];
        $port = $setting['port'];

        $this->db = new Redis();
        $this->db->connect($host, $port);
    }
    
    public function get($key)
    {
        return $this->db->get($key);
    }
    
    public function set($key, $value)
    {
        return $this->db->set($key, $value);
    }
}

這支類別是使用 Redis 這個類別來連接 Redis 資料庫。簡單的說,CacheRedis 依賴 Redis。那何謂注入呢?

注入 (Injection)

CacheRedis 改寫如下:

範例:/day-8/example-1/CacheRedis.php

class CacheRedis
{
    protected $db;

    public function __construct(Redis $redis)
    {
        $this->db = $redis;
    }
    
    public function get($key)
    {
        return $this->db->get($key);
    }
    
    public function set($key, $value)
    {
        return $this->db->set($key, $value);
    }
}

可以看到在建構子的參數:

public function __construct(Redis $redis)
{
    $this->db = $redis;
}

我們把整段實例化 Redis 的程式碼移到外面去了,改用參數的方式帶入,並使用型別提示 (type hint),規定建構子只能接受 Redis 類別實例化後的物件。

範例:/day-8/example-1/Example.php

<?php

include 'CacheRedis.php';

$host = '127.0.0.1';
$port = 6379;

// 實例化 Redis
$redis = new Redis();
$redis->connect($host, $port);

// 注入 Redis 類別產生的物件
$cache = new CacheRedis($redis);

$cache->set('day8', 'ok');

echo $cache->get('day8') . "\n";

這樣做的好處在於,更貼近封閉 - 開放原則,即是對修改封閉、對擴展開放
不需要為了要新增 Redis 要使用的參數,而去修改 CacheRedis。直接在外部實例化 Redis 後,將物件注入至 CacheRedis 使用。

依賴反轉原則 (Dependency inversion principle)

在前段的介紹範例中,有一個情況會讓 CacheRedis 無法使用。因為 Redis 這個類別是 C 語言寫的 PHP 擴充套件,假如使用 PHP 版本較舊,無法裝該套件,那就 GG 了。

另外有一個是由 PHP 寫成的 Redis 套件名叫 Predis,我們決定使用它,另一方面我們想要不管是 Redis 類別,或是 Predis 類別,往後都可以使用。那麼「依賴反轉原則」是時候派上用場了呀。

teeWrHg.png

如上圖 A。高層物件 CacheRedis 依賴於低層物件 Redis。我們要將高層物件 CacheRedis 轉而依賴抽象介面RedisInterface。並將低層物件 RedisPredis 轉而實現RedisInterface介面。

範例:/day-8/example-2/RedisInterface.php

interface RedisInterface
{
    public function get($key);

    public function set($key, $value);
}

範例:/day-8/example-2/CacheRedis.php

class CacheRedis
{
    protected $db;

    public function __construct(RedisInterface $redis)
    {
        $this->db = $redis;
    }
    
    // 以下忽略 ...
}

建構子改為依賴介面RedisInterface,任何實作此介面都能注入至 CacheRedis

範例:/day-8/example-2/PredisExt.php

class PredisExt implements RedisInterface
{
    protected $db;

    public function __construct($setting)
    {
        $this->db = new \Predis\Client($setting);
    }

    public function get($key)
    {
        return $this->db->get($key);
    }

    public function set($key, $value)
    {
        return $this->db->set($key, $value);
    }
}

由於 Predis 是別人寫好的套件,我們無法讓它直接實作 RedisInterface,因此 PredisExt 是一個串接 Predis 並實作介面方法的類別。

範例:/day-8/example-2/RedisExt.php

class RedisExt extends Redis implements RedisInterface
{
    public function __construct($setting)
    {
        $this->connect($setting['host'],$setting['port']);
    }
}

Redis 類別已經有實作 RedisInterface 介面的方法了,因此直接繼承即可。

範例:/day-8/example-2/Example.php

$redis = new RedisExt([
    'host' => '127.0.0.1',
    'port' => 6379
]);

$cache = new CacheRedis($redis);
$cache->set('day8', 'example 2 - redis - ok');
echo $cache->get('day8') . "\n";

注入實現界面方法的 RedisExtCacheRedis 中。

$redis = new PredisExt([
    'host' => '127.0.0.1',
    'port' => 6379
]);

$cache = new CacheRedis($redis);
$cache->set('day8', 'example 2 - predis - ok');
echo $cache->get('day8') . "\n";

注入實現界面方法的 PredisExtCacheRedis 中。

如此一來不管是 Predis 或者是 Redis 類別,往後都能不用再修改 CacheRedis的情況下,注入使用。

原則

以上濃縮為兩句話:

  • 高層物件不依賴低層物件,兩者都依賴介面。
  • 介面不依照物件的細節去定義方法,而是介面定義方法後,物件的細節針對介面來實作設計。

結論

依賴注入及反轉原則是在物件導向程式設計中非常重要的設計模式。請務必在往後設計自己的開放原始碼作品刻意地使用它。只要用過一次,就如同打通任督二脈,再回頭看理論描述,就會了解整個概念了。從做中學是最好的學習方法。

希望本篇對讀者們有幫助喔。我們明天見。

本篇原始碼可在此瀏覽


本文同步更新於 TerryL 部落格 Day 8 - PHP 設計模式:依賴注入 (Dependency Injection),歡迎前往討論。


上一篇
Day 7 - PHP 設計模式:工廠 (Factory)
下一篇
Day 9 - PHP 設計模式:觀察者 (Observer)
系列文
PHP 大師之路 - 開源的技術淬練30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
shiefu
iT邦新手 5 級 ‧ 2020-11-25 21:12:14

原始檔好像少Redis.php這個類別 !!

沒這個檔,RedisExt這個類別在extends會出錯 !

shiefu iT邦新手 5 級 ‧ 2020-11-25 21:48:47 檢舉

抱歉,我知道原因了,Redis是指原生的Redis

我要留言

立即登入留言