⛳
# デザインパターンを学ぶ #10 プロキシ(Proxy)
1. はじめに
Proxy(プロキシ)は本体(RealSubject)への代用オブジェクトを用意し、アクセスを間接化するパターン。遅延ロード、アクセス制御、キャッシュ、リトライなどを本体を変更せずに差し込めます。
2. Proxyとは
- Subject:クライアントが期待する共通インターフェース
- RealSubject:本体(重い処理・外部IOなど)
- Proxy:RealSubject と同じインターフェースを持ち、前後処理や条件分岐を肩代わりしてから本体へ委譲
3. 代表的な用途
- Virtual Proxy:重いオブジェクトを 遅延生成(画像/レポート/接続)
- Protection Proxy:権限チェックやレート制限
- Cache/Retry Proxy:外部APIやDBのキャッシュ・リトライ
- Remote Proxy:遠隔オブジェクトへの窓口(RPC等)
4. 実装例(PHP:外部APIに対するキャッシュ+レート制限付きProxy)
<?php
interface WeatherClient {
public function get(string $city): array; // 例: ['temp' => 28, 'unit' => 'C']
}
// 本体:実際に外部APIを叩くクライアント(重い/失敗しがち)
final class RealWeatherClient implements WeatherClient {
public function __construct(private string $endpoint, private string $token) {}
public function get(string $city): array {
// 擬似的な外部IO(本来は curl/file_get_contents 等)
// ここでは例として固定レスポンス
return ['temp' => 28, 'unit' => 'C', 'city' => $city, 'source' => 'real'];
}
}
// Proxy:同じインターフェースで、前後に制御を挟む
final class WeatherClientProxy implements WeatherClient {
private ?WeatherClient $real = null;
private array $cache = []; // city => ['data' => ..., 'ts' => time()]
private int $ttlSec = 60; // 簡易キャッシュTTL
private int $lastCallAt = 0; // レート制限用(最小間隔)
private int $minIntervalMs = 200; // 200ms間隔
public function __construct(
private string $endpoint,
private string $token
) {}
public function get(string $city): array {
// 1) キャッシュ
if (isset($this->cache[$city])) {
$hit = $this->cache[$city];
if (time() - $hit['ts'] < $this->ttlSec) {
return $hit['data'] + ['source' => 'cache'];
}
}
// 2) レート制限(最低間隔)
$elapsed = (int) (microtime(true) * 1000) - $this->lastCallAt;
if ($elapsed < $this->minIntervalMs) {
usleep(($this->minIntervalMs - $elapsed) * 1000);
}
// 3) 遅延生成(Virtual Proxy)
if ($this->real === null) {
$this->real = new RealWeatherClient($this->endpoint, $this->token);
}
// 4) 本体へ委譲 + 簡易リトライ
$this->lastCallAt = (int) (microtime(true) * 1000);
$retries = 2;
do {
try {
$res = $this->real->get($city);
// 5) キャッシュ保存
$this->cache[$city] = ['data' => $res, 'ts' => time()];
return $res;
} catch (\Throwable $e) {
if ($retries-- <= 0) throw $e;
usleep(100 * 1000); // 100ms待って再試行
}
} while (true);
}
}
// 使い方
$client = new WeatherClientProxy('https://api.example.com/weather', 'TOKEN');
// 1回目:本体へ、2回目:キャッシュから
print_r($client->get('Tokyo')); // → source: real
print_r($client->get('Tokyo')); // → source: cache
5. メリット
- 責務分離:本体はコア機能に集中、横断的関心(権限/キャッシュ/レート制限等)はProxyへ
- 差し替え容易:同じインターフェースなのでDIで本体⇄Proxyを切り替え可能
- 非侵入的拡張:本体コードを変更せず振る舞い追加(OCP)
6. 注意点
- 階層が増えるとデバッグが追いにくい(ログ/トレーシング推奨)
- 乱用するとどのProxyが有効か把握しにくい(命名・構成の一貫性)
- 共有キャッシュやリトライなど副作用の場所を明確に(スレッド/プロセス/リクエスト境界)
7. 他パターンとの違い
- Decorator:機能追加が主目的(透過的に積み重ね)。Proxyはアクセスの間接化(権限・遅延・キャッシュ)。
- Adapter:インターフェース変換。Proxyは同一インターフェース。
- Facade:サブシステムの簡略窓口。Proxyは同じ窓口の前段で制御。
8. テスト観点
- 本体が呼ばれる回数(キャッシュヒットで減るか)
- レート制限や権限チェックが期待通りにブロックするか
- 例外時のリトライ・フォールバック動作
9. まとめ
Proxyは、本体と同じインターフェースを持つ代用オブジェクトで、アクセスの前段に**制御(キャッシュ/権限/レート制限/遅延生成)**を差し込むパターン。
本体を変えずに運用上の要件を満たせるのが強みです。次は構造パターン残りの Flyweight(フライウェイト) に進むと流れが自然です。
Discussion