アノテーションを使ってインターセプタを適用する
JavaScriptでインターセプトの方法だと、インスタンスの生成ごとにapplyInterceptor()行う必要があって面倒です。クラス定義にアノテーションを書いておくと勝手にインターセプタを設定してくれるようにしたい!
アノーテョン
とはいえ、JavaScriptにアノテーションはありません。そこで、関数やクラス関数に meta 属性を勝手に作って、そこにアノテーションを書くことにします!
// アノテーションするクラス function KittenService( ) {} KittenService.prototype = { run: function() { stdout.innerHTML += "run!<br/>"; }, stop: function() { stdout.innerHTML += "stop!<br/>"; }, jump: function() { stdout.innerHTML += "jump!<br/>"; } } // これを関数のアノテーションとしよう! KittenService.prototype.jump.meta = { echo: {} } KittenService.prototype.stop.meta = { echo: {} } // クラスのアノテーションはこうする! KittenService.prototype.meta = { intercept: true } // フィールドのアノテーションはこれから考える!
「フィールドのアノテーションは?」とか、「関数上書きしたら消え去りそう」とか、いろいろつっこみどころはありますがとりあえずはこれで。
サービスファクトリ
アノーテョンができた?ので、次はサービスファクトリです。「サービスファクトリ」はオブジェクトを生成するクラスで、このときアノテーション参照して、必要であればインターセプタを設定します。
/** * サービスファクトリ * @param {Object} recipe インターセプタのハッシュ */ function ServiceFactory( recipe ) { this.recipe = recipe; } ServiceFactory.prototype = { /** * クラスのインスタンスを生成する。 * @param {Object} clazz クラス * @param {Object} arg コンストラクタ引数 */ create: function( clazz) { // インスタンス生成 var obj = new clazz(); // interceptアノテーション付きのクラスにのみインターセプタを適用。 if ( obj.meta && obj.meta.intercept ) { for ( var key in obj ) { if ( typeof obj[key] != "function" ) { continue; } if ( !obj[key].meta ) { continue; } for ( var meta in obj[key].meta ) { // 関数に付いているアノテーションと同じ名前のインターセプタがレシピにあれば登録する。 if (!this.recipe[meta]) { continue; } var thiz = this; (function() { var interceptor = thiz.recipe[meta]; var x = key; var original = obj[key]; obj[x] = function( ) { // インターセプターを実行する関数に置き換える。 return interceptor( x, obj, original, arguments ); } obj[x].meta = original.meta; })(); } } } return obj; } }
使ってみる
サービスファクトリを使うと、こんな感じでインターセプタを適用したオブジェクトを生成できます。
// サービスファクトリを生成 // 引数でインターセプタのハッシュを指定。 // この場合、echoが付いていれば関数呼び出しの前後で関数名を表示する。 var sf = new ServiceFactory( { echo: function( f, target, original, arguments ) { stdout.innerHTML += "start. " + f + "<br/>"; try { return original.apply( target, arguments ); } finally { stdout.innerHTML += "end. " + f + "<br/>"; } } }); // サービスの作成 var service = sf.create( KittenService ); service.run(); service.stop(); service.jump();