Hatena::ブログ(Diary)

akimatter このページをアンテナに追加 RSSフィード

2006-09-17

JavaScriptでアスペクト指向

| 08:31 |  JavaScriptでアスペクト指向を含むブックマーク  JavaScriptでアスペクト指向のブックマークコメント

JavaScriptオブジェクトアスペクトを適用するコードを書いてみた。

アスペクトは単なる関数として書きます。

JavaScriptアスペクト指向やってもあんまりおいしいところはないと思ってたけどそれなりに使える。

Dateオブジェクトアスペクトを適用するテストケース。

function test_Object_Aspect_around() {
    var d1 = new Date("2006/09/16");
    var status = null;
    var aspect = function(invocation){
        var oldValue = invocation.target.getFullYear();
        var result = invocation.proceed();
        status = (oldValue == invocation.target.getFullYear())?"nochange":"changed";
        return result;
    };
    Object.Aspect.around(d1, "setFullYear", aspect);
    d1.setFullYear(2006);
    assertEquals("nochange", status);
    assertEquals(2006, d1.getFullYear());
    d1.setFullYear(2007);
    assertEquals("changed", status);
    assertEquals(2007, d1.getFullYear());
}

function test_Object_Aspect_after() {
    var d1 = new Date("2006/09/16");
    var log = null;
    var aspect = function(invocation){
        log = invocation.methodName+"("+ $A(invocation.arguments).join(",") +")";
    };
    Object.Aspect.after(d1, ["setFullYear","setMonth"], aspect);
    d1.setFullYear(2006);
    assertEquals("setFullYear(2006)", log);
    assertEquals(2006, d1.getFullYear());
    log = null;
    d1.setMonth(3);
    assertEquals("setMonth(3)", log);
    assertEquals(3, d1.getMonth());
}

function test_Object_Aspect_before() {
    var d1 = new Date("2006/09/16");
    var log = null;
    var aspect = function(invocation){
        log = invocation.methodName+"("+ $A(invocation.arguments).join(",") +")";
        if (invocation.methodName == "setMonth"){
            log += " cancelled";
            invocation.cancelled = true;
            return;
        }
    };
    Object.Aspect.before(d1, ["setFullYear","setMonth"], aspect);
    d1.setFullYear(2006);
    assertEquals("setFullYear(2006)", log);
    assertEquals(2006, d1.getFullYear());
    log = null;
    d1.setMonth(3);
    assertEquals("setMonth(3) cancelled", log);
    assertEquals(8, d1.getMonth());
}

以下実装コードです。

Object.Aspect = {
    _around: function(target, methodName, aspect){
        var method = target[methodName];
        target[methodName] = function() {
            var invocation = {
                "target":this, 
                "method":method,
                "methodName":methodName,
                "arguments":arguments,
                "proceed": function(){
                    return method.apply(target, this.arguments);
                }
            };
            return aspect.apply(null, [invocation]);
        };
    },
    _before: function(target, methodName, aspect){
        var method = target[methodName];
        target[methodName] = function() {
            var invocation = {
                "target":this, 
                "method":method,
                "methodName":methodName,
                "arguments":arguments,
                "cancelled": false
            };
            aspect.apply(null, [invocation]);
            return (invocation["cancelled"]) ?
                invocation["result"] : method.apply(target, arguments);
        };
    },
    _after: function(target, methodName, aspect){
        var method = target[methodName];
        target[methodName] = function() {
            var invocation = {
                "target":this, 
                "method":method,
                "methodName":methodName,
                "arguments":arguments
            };
            invocation["result"] = method.apply(target, arguments);
            return aspect.apply(null, [invocation]);
        };
    },
    
    _apply: function(func, target, methodNames, aspect){
        methodNames = methodNames||this.getMethodNames(target);
        methodNames = (methodNames.each)?methodNames:[methodNames];
        methodNames.each(function(methodName){
            func(target, methodName, aspect);
        });
    },
    
    getMethodNames: function(target){
        var result = [];
        for(var attr in target) {
            try{
                var value = target[attr];
                if (value.constructor == Function)
                    result.push(attr);
            }catch(ex){
            }
        }
        return result;
    },
    
    around: function(target, methodNames, aspect){
        this._apply(this._around, target, methodNames, aspect);
    },
    before: function(target, methodNames, aspect){
        this._apply(this._before, target, methodNames, aspect);
    },
    after: function(target, methodNames, aspect){
        this._apply(this._after, target, methodNames, aspect);
    }
}
最近読んだ本
  • 情熱プログラマー ソフトウェア開発者の幸せな生き方
  • 禁煙セラピー[セラピーシリーズ]
  • 入門git
  • 入門Git
  • もやしもん(8) (イブニングKC)
  • JRuby 徹底入門
  • 入門Subversion Windows/Linux対応
  • Ship It! ソフトウェアプロジェクト 成功のための達人式ガイドブック
  • プログラミングRuby 第2版 言語編
  • プログラミングRuby 第2版 ライブラリ編