こんにちは、KOUKIです。
この記事では、デザインパターンの一つであるProxyパターンについて紹介します。
<目次>
デザインパターンまとめ
シチュエーション
ProxyパターンのProxyは、「代理人」という意味です。本人が行いたい仕事をこの代理人が代わって実行するイメージを持ってもらえればわかりやすいと思います。
例えば、とある仮想通貨APIへリクエストを送りたいとします。
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 |
package main import "log" // 仮想通貨API type CryptoCurrencyAPI struct{} // コンストラクタ func NewCryptoCurrencyAPI() *CryptoCurrencyAPI { return &CryptoCurrencyAPI{} } func (c *CryptoCurrencyAPI) getValue(coin string) string { log.Println("Calling External API...") switch coin { case "Bitcoin": return "$10" case "Litecoin": return "$5" case "Ethereum": return "$20" default: return "$0" } } |
このコードの呼び出しは、次のようになります。
1 2 3 4 5 |
func main() { api := NewCryptoCurrencyAPI() // 直接APIを呼び出す log.Println(api.getValue("Bitcoin")) } |
これは、代理人を使わず、クライアントが直接APIを呼び出しています。
1 2 3 |
$ go run main.go 2021/05/16 07:05:30 Calling External API... 2021/05/16 07:05:30 $10 |
一見問題がないように思えますが、APIへアクセスしているということは、その間にネットワーク通信が走っているということです。
アクセスが少ない内は特に問題はありませんが、これが大量のリクエストとなった場合はどうなるでしょうか?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
func main() { api := NewCryptoCurrencyAPI() // 直接APIを呼び出す log.Println(api.getValue("Bitcoin")) log.Println(api.getValue("Litecoin")) log.Println(api.getValue("Ethereum")) log.Println(api.getValue("Bitcoin")) log.Println(api.getValue("Litecoin")) log.Println(api.getValue("Ethereum")) log.Println(api.getValue("Bitcoin")) log.Println(api.getValue("Litecoin")) log.Println(api.getValue("Ethereum")) log.Println(api.getValue("Bitcoin")) log.Println(api.getValue("Litecoin")) log.Println(api.getValue("Ethereum")) log.Println(api.getValue("Bitcoin")) log.Println(api.getValue("Litecoin")) log.Println(api.getValue("Ethereum")) } |
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 |
$ go run main.go 2021/05/16 07:08:33 Calling External API... 2021/05/16 07:08:33 $10 2021/05/16 07:08:33 Calling External API... 2021/05/16 07:08:33 $5 2021/05/16 07:08:33 Calling External API... 2021/05/16 07:08:33 $20 2021/05/16 07:08:33 Calling External API... 2021/05/16 07:08:33 $10 2021/05/16 07:08:33 Calling External API... 2021/05/16 07:08:33 $5 2021/05/16 07:08:33 Calling External API... 2021/05/16 07:08:33 $20 2021/05/16 07:08:33 Calling External API... 2021/05/16 07:08:33 $10 2021/05/16 07:08:33 Calling External API... 2021/05/16 07:08:33 $5 2021/05/16 07:08:33 Calling External API... 2021/05/16 07:08:33 $20 2021/05/16 07:08:33 Calling External API... 2021/05/16 07:08:33 $10 2021/05/16 07:08:33 Calling External API... 2021/05/16 07:08:33 $5 2021/05/16 07:08:33 Calling External API... 2021/05/16 07:08:33 $20 2021/05/16 07:08:33 Calling External API... 2021/05/16 07:08:33 $10 2021/05/16 07:08:33 Calling External API... 2021/05/16 07:08:33 $5 2021/05/16 07:08:33 Calling External API... 2021/05/16 07:08:33 $20 |
ご覧の通り、何回もAPIを呼び出すことになり、圧迫感が半端ない状況になりました^^
この様な問題の解決方法として、Proxyパターンが使えます。
Proxyパターンを実装する
前述の通り、Proxyパターンは代理人です。それでは、何を代理すれば良いでしょうか?
決まっていますね。APIの処理を代理すればいいのです。
例えば、APIへの呼び出しの代わりに、キャッシュ(代理人)からデータを返却できるようにしてみましょう。
Proxy インターフェースの実装
Proxyは、本人(API)の代理人として処理を実行するので、本人と同じメソッドを持たせたいです。その為に、Proxyインターフェースを実装します。
1 2 3 |
type CryptoCurrency interface { getValue(string) string } |
Proxy構造体を実装
次に、Proxy構造体を実装します。
1 2 3 4 |
type ProxyCryptoCurrencyAPI struct { api *CryptoCurrencyAPI cache *sync.Map // キャッシュ } |
キャッシュには、syncパッケージのMapを使っています。
Proxyコンストラクタの定義
続いて、Proxyコンストラクタを定義します。
1 2 3 4 5 6 |
func NewProxyCryptoCurrencyAPI() *ProxyCryptoCurrencyAPI { return &ProxyCryptoCurrencyAPI{ api: &CryptoCurrencyAPI{}, cache: &sync.Map{}, } } |
Proxyメソッドの定義
Proxyメソッドは、インターフェースに定義したgetValueを実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
func (p *ProxyCryptoCurrencyAPI) getValue(coin string) string { key := fmt.Sprintf("coin=%s", coin) // キャッシュ確認 val, ok := p.cache.Load(key) if ok { log.Println("Calling Proxy Cache...") return val.(string) } // キャッシュがない場合はAPIへリクエスト apiVal := p.api.getValue(coin) // キャッシュにデータを保存 p.cache.Store(key, apiVal) return apiVal } |
sync.Mapでキャッシュがあるかチェックし、存在する場合はキャッシュからデータを返却し、存在しない場合は、APIへリクエストを転送しています。
まさに、代理人ですね!
使ってみよう
先ほどと同じ方法で、APIを呼び出します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
func main() { api := NewProxyCryptoCurrencyAPI() // 直接APIを呼び出す log.Println(api.getValue("Bitcoin")) log.Println(api.getValue("Litecoin")) log.Println(api.getValue("Ethereum")) log.Println(api.getValue("Bitcoin")) log.Println(api.getValue("Litecoin")) log.Println(api.getValue("Ethereum")) log.Println(api.getValue("Bitcoin")) log.Println(api.getValue("Litecoin")) log.Println(api.getValue("Ethereum")) log.Println(api.getValue("Bitcoin")) log.Println(api.getValue("Litecoin")) log.Println(api.getValue("Ethereum")) log.Println(api.getValue("Bitcoin")) log.Println(api.getValue("Litecoin")) log.Println(api.getValue("Ethereum")) } |
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 |
$ go run main.go 2021/05/16 07:33:24 Calling External API... 2021/05/16 07:33:24 $10 2021/05/16 07:33:24 Calling External API... 2021/05/16 07:33:24 $5 2021/05/16 07:33:24 Calling External API... 2021/05/16 07:33:24 $20 2021/05/16 07:33:24 Calling Proxy Cache... 2021/05/16 07:33:24 $10 2021/05/16 07:33:24 Calling Proxy Cache... 2021/05/16 07:33:24 $5 2021/05/16 07:33:24 Calling Proxy Cache... 2021/05/16 07:33:24 $20 2021/05/16 07:33:24 Calling Proxy Cache... 2021/05/16 07:33:24 $10 2021/05/16 07:33:24 Calling Proxy Cache... 2021/05/16 07:33:24 $5 2021/05/16 07:33:24 Calling Proxy Cache... 2021/05/16 07:33:24 $20 2021/05/16 07:33:24 Calling Proxy Cache... 2021/05/16 07:33:24 $10 2021/05/16 07:33:24 Calling Proxy Cache... 2021/05/16 07:33:24 $5 2021/05/16 07:33:24 Calling Proxy Cache... 2021/05/16 07:33:24 $20 2021/05/16 07:33:24 Calling Proxy Cache... 2021/05/16 07:33:24 $10 2021/05/16 07:33:24 Calling Proxy Cache... 2021/05/16 07:33:24 $5 2021/05/16 07:33:24 Calling Proxy Cache... 2021/05/16 07:33:24 $20 |
最初の呼び出し時のみAPIを呼び、それ以降はキャッシュからデータを取得している様子がわかりますね^^
まとめ
Proxyパターンは、Decoratorパターンと少し似てますね。
どこの誰に責任(処理)を持たせるか、そういったことを考えながらプログラミングをするのも結構面白いです^^
それでは、また!
Go言語まとめ
ソースコード
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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
package main import ( "fmt" "log" "sync" ) type CryptoCurrency interface { getValue(string) string } // 仮想通貨API type CryptoCurrencyAPI struct{} // コンストラクタ func NewCryptoCurrencyAPI() *CryptoCurrencyAPI { return &CryptoCurrencyAPI{} } func (c *CryptoCurrencyAPI) getValue(coin string) string { log.Println("Calling External API...") switch coin { case "Bitcoin": return "$10" case "Litecoin": return "$5" case "Ethereum": return "$20" default: return "$0" } } type ProxyCryptoCurrencyAPI struct { api *CryptoCurrencyAPI cache *sync.Map } func NewProxyCryptoCurrencyAPI() *ProxyCryptoCurrencyAPI { return &ProxyCryptoCurrencyAPI{ api: &CryptoCurrencyAPI{}, cache: &sync.Map{}, } } func (p *ProxyCryptoCurrencyAPI) getValue(coin string) string { key := fmt.Sprintf("coin=%s", coin) // キャッシュ確認 val, ok := p.cache.Load(key) if ok { log.Println("Calling Proxy Cache...") return val.(string) } // キャッシュがない場合はAPIへリクエスト apiVal := p.api.getValue(coin) // キャッシュにデータを保存 p.cache.Store(key, apiVal) return apiVal } func main() { api := NewProxyCryptoCurrencyAPI() // 直接APIを呼び出す log.Println(api.getValue("Bitcoin")) log.Println(api.getValue("Litecoin")) log.Println(api.getValue("Ethereum")) log.Println(api.getValue("Bitcoin")) log.Println(api.getValue("Litecoin")) log.Println(api.getValue("Ethereum")) log.Println(api.getValue("Bitcoin")) log.Println(api.getValue("Litecoin")) log.Println(api.getValue("Ethereum")) log.Println(api.getValue("Bitcoin")) log.Println(api.getValue("Litecoin")) log.Println(api.getValue("Ethereum")) log.Println(api.getValue("Bitcoin")) log.Println(api.getValue("Litecoin")) log.Println(api.getValue("Ethereum")) } |
コメントを残す
コメントを投稿するにはログインしてください。