Proxyパターンとは
構造に関するデザインパターンの一つ。あるオブジェクトの代理・代用を提供し、元オブジェクトへのアクセス制御等の別処理を行う。Proxyが担う役割毎にいくつかの種類に分類される。
Proxyの一例
Virtual Proxy (仮想プロキシ)
Serviceオブジェクトの遅延初期化(lazy initialization)を行う。クライアントからの要求に対して、基本的にProxyで対処し、必要となった時点でServiceオブジェクトの生成・初期化を行う。
Serviceオブジェクトの生成にコストがかかる場合に利用される。
Remote Proxy (遠隔プロキシ)
ネットワークを介してリモートサービスのメソッド呼び出しを行う。JavaのRMI (Remote Mtthod Invocation: 遠隔メソッド呼び出し)がこれに相当する。
Access Proxy (アクセスプロキシ)
Serviceオブジェクトの利用に対して、特定のクライアントからのみ利用できるように制限を加える。
Logging Proxy (ロギングプロキシ)
Serviceオブジェクトへ送られるリクエストのログを記録する。
Cache Proxy (キャッシュプロキシ)
Serviceオブジェクトへのリクエスト結果を保持し、必要ならばこのリクエストを返す。
長所
- クライアントに影響を与えることなく、サービスオブジェクトの制御を行うことができる。
- サービスオブジェクトが準備中等でもプロキシ側で対応できる。
- サービスやクライアントに変更することなく、新規プロキシを追加できる。(開放閉鎖の原則)
- 外部ライブラリ等に対しても、新規にロギングやアクセス制御を行える。
短所
- コードが複雑になる。
利用場面
- サービスオブジェクトの生成にコストがかかり、実際に利用するまで初期化を遅らせたい場合(仮想プロキシ)
- 特定のクライアントのみがサービスオブジェクトを利用できるようにしたい場合(アクセスプロキシ)
- サービスオブジェクトへのリクエストログを残したい場合(ロギングプロキシ)
- サービスへのリクエスト結果が大きい場合(キャッシュプロキシ)
クラス図

Service Interface
ServiceとProxyがともに実装するべきインターフェースを宣言する。これを実装することでクライアントはProxyをServiceと同様の方法で利用することができる。
Service
外部作成のライブラリ等の既存システム。何らかのビジネスロジックを提供する。
Proxy
Service Interfaceを実装し、Serviceオブジェクトへの参照を保持する。Proxyはクライアントからの要求に対して何らかの処理(遅延初期化・ロギング・キャッシュ処理など)を行い、その後必要ならばServiceオブジェクトへ仕事を委譲する。
またServiceオブジェクトのライフサイクルを管理する役割を持つ。
実装例
Service Interface
package main
type Server interface {
HandleRequest(url, method string, body map[string]string) (int, string)
}
Service
package main
import "fmt"
type App struct {}
func NewApp() *App {
return &App{}
}
func (a *App) HandleRequest(url, method string, body map[string]string) (int, string) {
fmt.Println("App received a request.")
if url == "/users" && method == "GET" {
return 200, "['tanaka', 'kato', 'yamamoto']"
}
return 404, "Not Found"
}
Proxy
package main
import (
"fmt"
"sync"
)
var mu sync.Mutex
type Proxy struct {
app *App
cache map[string](map[int]string) // ex: {url_method: {statusCode: response}}
}
func NewProxy() *Proxy {
return &Proxy{
cache: map[string](map[int]string){},
}
}
func (p *Proxy) HandleRequest(url, method string, body map[string]string) (int, string) {
// 認証 (Access Proxy: アクセスプロキシ)
user := body["user"]
password := body["password"]
if ok := p.checkAuth(user, password); !ok {
return 401, "Unauthorized"
}
// 遅延生成 (Virtual Proxy: 仮想プロキシ)
p.createApp()
// キャッシュ (Cache Proxy: キャッシュプロキシ)
key := fmt.Sprintf("%s_%s", url, method)
if res, ok := p.cache[key]; ok {
for code, response := range res {
fmt.Println("response from proxy cache.")
return code, response
}
}
statusCode, response := p.app.HandleRequest(url, method, body)
p.cache[key] = map[int]string{statusCode: response}
return statusCode, response
}
func (p *Proxy) checkAuth(user, password string) bool {
if user == "test_user" && password == "password" {
return true
}
return false
}
func (p *Proxy) createApp() {
mu.Lock()
if p.app == nil {
fmt.Println("create App instance.")
p.app = NewApp()
}
mu.Unlock()
}
動作確認
package main
import "fmt"
const (
USER = "test_user"
WRONG_USER = "wrong_user"
PASSWORD = "password"
GET = "GET"
URL = "/users"
)
func main() {
body := map[string]string{
"password": PASSWORD,
}
proxy := NewProxy()
// 不正ユーザー
body["user"] = WRONG_USER
code, res := proxy.HandleRequest(URL, GET, body)
fmt.Printf("Status Code: %d, Response: %s\n\n", code, res)
// 正規ユーザー
body["user"] = USER
code, res = proxy.HandleRequest(URL, GET, body)
fmt.Printf("Status Code: %d, Response: %s\n\n", code, res)
// キャッシュ
code, res = proxy.HandleRequest(URL, GET, body)
fmt.Printf("Status Code: %d, Response: %s\n\n", code, res)
// 不正URL
code, res = proxy.HandleRequest("", GET, body)
fmt.Printf("Status Code: %d, Response: %s\n\n", code, res)
}
>> go run .
Status Code: 401, Response: Unauthorized
create App instance.
App received a request.
Status Code: 200, Response: ['tanaka', 'kato', 'yamamoto']
response from proxy cache.
Status Code: 200, Response: ['tanaka', 'kato', 'yamamoto']
App received a request.
Status Code: 404, Response: Not Found
コメント