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

Pattern: 代理模式

Class Diagram: 快取代理


情境:以下是某搜尋功能

  • 客戶端程式碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?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);
}
}

  • 實體資料庫(供存取資料用)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?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 [];
}
}

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

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


需求一:實現快取代理

  • 首先定義讀取介面
1
2
3
4
5
6
7
8
9
10
11
12
<?php

namespace App\ProxyPattern\Cache\Contracts;

interface Readable
{
/**
* @param string $keyword
* @return array
*/
public function read(string $keyword): array;
}
  • 實體資料庫實現讀取介面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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 [];
}
}

  • 實作快取代理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?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;
}
}

  • 修改客戶端程式碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?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);
}
}

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


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

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

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

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

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