iT邦幫忙

第 12 屆 iThome 鐵人賽

2
Software Development

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

Day36. 範例:快取代理(代理模式)

本文同步更新於blog

情境:以下是某搜尋功能

  • 客戶端程式碼
<?php

namespace App\ProxyPattern\Cache;

use App\ProxyPattern\Cache\Database;

class Program
{
    /**
     * @var Database
     */
    protected $database;

    public function __construct()
    {
        $this->database = new Database();
    }

    /**
     * @param string $keyword
     * @return array
     */
    public function search(string $keyword): array
    {
        return $this->database->read($keyword);
    }
}

  • 實體資料庫(供存取資料用)
<?php

namespace App\ProxyPattern\Cache;

class Database
{
    /**
     * @param string $keyword
     * @return array
     */
    public function read(string $keyword): array
    {
        if ($keyword == 'sushi') {
            return ['Bear Sushi', 'Lin Sushi', 'Alysa Sushi'];
        }

        return [];
    }
}

老闆希望搜尋時,若是已搜尋過的資料,
便由快取返回,不再呼叫實體資料庫。

讓我們用代理模式改造它。


需求一:實現快取代理

  • 首先定義讀取介面
<?php

namespace App\ProxyPattern\Cache\Contracts;

interface Readable
{
    /**
     * @param string $keyword
     * @return array
     */
    public function read(string $keyword): array;
}
  • 實體資料庫實現讀取介面
<?php

namespace App\ProxyPattern\Cache;

use App\ProxyPattern\Cache\Contracts\Readable;

class Database implements Readable
{
    /**
     * @param string $keyword
     * @return array
     */
    public function read(string $keyword): array
    {
        if ($keyword == 'sushi') {
            return ['Bear Sushi', 'Lin Sushi', 'Alysa Sushi'];
        }

        return [];
    }
}

  • 實作快取代理
<?php

namespace App\ProxyPattern\Cache;

use App\ProxyPattern\Cache\Contracts\Readable;

class CacheProxy implements Readable
{
    /**
     * @var array
     */
    protected $cached = [];

    /**
     * @var Database
     */
    protected $database;

    public function __construct()
    {
        $this->database = new Database();
    }

    /**
     * @param string $keyword
     * @return array
     */
    public function read(string $keyword): array
    {
        if (isset($this->cached[$keyword])) {
            return $this->cached[$keyword];
        }

        $result = $this->database->read($keyword);
        $this->cached[$keyword] = $result;

        return $result;
    }
}

  • 修改客戶端程式碼
<?php

namespace App\ProxyPattern\Cache;

use App\ProxyPattern\Cache\CacheProxy;

class Program
{
    /**
     * @var CacheProxy
     */
    protected $proxy;

    public function __construct()
    {
        $this->proxy = new CacheProxy();
    }

    /**
     * @param string $keyword
     * @return array
     */
    public function search(string $keyword): array
    {
        return $this->proxy->read($keyword);
    }
}

這下子客戶端搜尋時,若快取代理有資料,便會直接返回結果。


[單一職責原則]
我們將實體類別代理類別視作兩種不同的職責。
代理類別主要處理訪問實體類別的行為。

[開放封閉原則]
當我們需要實現不屬於實體類別的職責時(例如:關鍵字被搜尋次數),
我們可以在代理類別中實現,不須修改實體類別的程式碼。

若有需要其他控制訪問的職責時,也可以新增代理類別。

除了上述的提出介面的委派方法外,
也有人用繼承的手法修改行為,但我個人比較不喜歡。

最後附上類別圖:
https://ithelp.ithome.com.tw/upload/images/20201019/20111630yJeWHi10ip.png
(註:若不熟悉 UML 類別圖,可參考UML類別圖說明。)

ʕ •ᴥ•ʔ:代理類別就像是實體類別的經紀人一樣。


上一篇
Day35. 代理模式
下一篇
Day37. 原型模式
系列文
你終究都要學設計模式的,那為什麼不一開始就學呢?57
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言