LoginSignup
7
5

More than 5 years have passed since last update.

python,ruby,PHPで動的プロキシ

Last updated at Posted at 2016-12-20

はじめに

この記事は「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を使います。

a.rb
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という別のメソッドが用意されています。

a.php
<?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"
}

注意すること

使いすぎると挙動を読みづらいコードになりがちです。ご利用は計画的に。

7
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
5