はじめに
この記事は「WACUL Advent Calendar 2016」の20日目です。
最近、個人的にメタプログラミングrubyを読み返したのと仕事で一部使ったり語れる新鮮なネタが無いといった経緯があり、今回は動的プロキシについて書きます。
動的プロキシとは
実は、この言葉がどこまで一般的なのか自信がないのですが… 個人的には「定義してないメソッドの名前とパラメータを受け取って、目的の処理を別のオブジェクトに委譲するようなプログラミングパターン」と認識しています。
どんな時に使うのか
外部システムのラッパーを書きたい時に便利です。
例えば、外部のREST apiを利用するクライアントライブラリを書きたいとします。話を単純にするために、GETに限定して考えます。
GET /api/hoge
を実行するには、例えばpythonでrequestsを使えば
import requests
class Client:
def hoge(self):
return requests.get('/api/hoge')
client = Client()
client.hoge()
などすれば良いです。何も難しくありません。
しかし、この場合だとfuga,piyoなど増えてくると
class Client:
def hoge(self):
return requests.get('/api/hoge')
def fuga(self):
return requests.get('/api/fuga')
def piyo(self):
return requests.get('/api/piyo')
などとコピペが増えてきて大変です。
そこで、「メソッドやプロパティが見つからない時に呼ばれる特殊メソッド」を使います。pythonの場合メソッドもプロパティも等しくオブジェクトの属性なので、__getattr__
が使えます。
class Client:
def __getattr__(self, name):
def _func(*args, **kwargs):
return requests.get('/api/' + name)
return _func
これで、client.hoge()もclient.fuga()もclient.piyo()も対応できますし、何より良いのは今後さらにapiが追加されてもClientクラスをメンテする必要がありません。また、_func
の実装次第ですが、client.method に渡すパラメータを自由にハンドリングできます。
RubyとPHPで動的プロキシ
まず、Rubyでやります。今回は単純にコード量を減らすだけでなく、未知の仕様にまで動的に対応させたいのでmethod_missingを使います。
class A
def method_missing(name, *args, &blk)
p name.to_s
p args
return if not block_given?
blk.call
end
end
a = A.new
a.hoge
a.fuga(1, 2, 3)
a.piyo do |i, j|
p "piyopiyo"
end
実行するとこうなります
"hoge"
[]
"fuga"
[1, 2, 3]
"piyo"
[]
"piyopiyo"
次にPHPです。__call
を使います。昔、仕事で書いていた書かされていた時に覚えました。Reflectionと組み合わせると良い感じです。静的メソッドの場合は __callStatic
という別のメソッドが用意されています。
<?php
class A
{
public function __call($name, $args)
{
var_dump($name);
var_dump($args);
}
}
$a = new A();
$a->hoge();
$a->fuga("fugaarg");
cliで実行するとこうなります
string(4) "hoge"
array(0) {
}
string(4) "fuga"
array(1) {
[0]=>
string(7) "fugaarg"
}
注意すること
使いすぎると挙動を読みづらいコードになりがちです。ご利用は計画的に。