読者です 読者をやめる 読者になる 読者になる

ECMA3 での OOP 思考実験

型仙人と恐れられた私も、さすがに ECMA3 の仕様をちゃんと読んだら考えが変わってきた。

ECMA3 が提供する OOP に使えそうな主な特徴

  • 動的な具象オブジェクトベースの prototype チェーン
  • 動的なオブジェクト
  • 静的なスコープチェーン

それぞれの主な使い途

動的 prototype チェーン

AOP, 動的に取ったり外したりするもの。 trait もか。
prototype は対象オブジェクトにつき一段だけ動的に切り替えながら使うのが吉か(プロトタイプのプロトタイプを…といった操作は禁忌…というか、あんまりメリット無い。詳しく書くと、対象オブジェクトAの__proto__がBだとすれば、 Aの__proto__.__proto__はもうAの関心範囲ではなくてBの__proto__と考えるのがECMA3的)

動的なオブジェクト

mixin, trait, ダックタイプ, 静的タイプではなくふるまいによる契約

静的なスコープチェーン

クロージャ
OOP的にどう活かすか?別に無理に活かさなくていいけど。

例えばAOP

/**
 * Aspect binding
 */
var Aspect = function(definitions) {
    //
};
Aspect.Klass = function() {
    this.getBaseName = function() {
        return this.constructor.prototype.constructor.name;
    };
    this.before = function(methodName) {
        this.run(points[this.getBaseName()+'.'+methodName].before);
    };
    this.after = function(methodName) {
        this.run(points[this.getBaseName()+'.'+methodName].after);
    };
    this.run = function(points) {
        for ( var i=0; l=points; i<l; i++ ) {
            points[i]();
        }
    };
};

/**
 * aspected klass declaration
 */
Function.prototype.aspected = function() {
    var Klass = Aspect.Klass;
    var base  = this();
    this.name = this.toString().match(/function\s([^(]+)/)[1];
    for ( var i in base ) {
        if ( base.hasOwnProperty(i) && typeof base[i] == 'function' ) {
            Klass[i] = function() {
                this.before(i);
                this.constructor.prototype[i]();
                this.after(i);
            };
        }
    }
    Klass.prototype = base;
    return new klass;
};




/**
 * Test Aspected klass
 */
var Test = function Test() {
    this.testMethodA = function() {
    };
    this.testMethodB = function() {
    };
    this.setSomeProperty = function() {
    };
}.aspected();

/**
 * Aspect declaration
 */
Aspect({
    XHRLogAspect : function() {
        // define & set Advice
        this.point('Test.testMethodA').before(
            function(){
                // some logging work before A
            }
        );
        this.point('*.testMethodB').after(
            function(){
                // some logging work after B
            }
        );

        // define & retrieve point
        var somePoint = this.point('*.set*');

        // set Advice
        somePoint.before(
            function() {
                // some logging work before C
            }
        );
        somePoint.after(
            function() {
                // some logging work after C
            }
        );
    }
});

とかか。動かしてないけど。

このサンプルの Pros
  • Aspect 主体のメンタルモデル
このサンプルの Cons
  • prototype チェーンの動的さを活かせていない。 Aspect 主体ではなく対象オブジェクト主体のインターフェースにすべきか?。
  • namespace への考慮。
  • そういえば新小岩の巨人に「before/afterじゃなくてaroundが欲しいんですよ」って言われた。
Test.pushAspect(XHRLogAspect);
Test.removeAspect(XHRLogAspect);

みたいにアスペクトを付けたり外したりするみたいな?でアスペクトってそんな使い方するかなあ…。アスペクト自体ちゃんと意識して使ってないから使い途が見えてなくてちゃんと思考できない。巨人に訊いてみた。

18:33 トランザクション、例外処理、リソース管理
18:33 同期処理
18:33 メモイズ
18:34 ProxyPatternとかDecoratorPatternつかうところほとんど

cross-cutting concern?
・ Thread-Safe,
・ Caching/Memoize,
・ Authenticate,
・ Logging,
・ Assertion/Validation
・ DB Transaction/Exception Handling

18:39 横断する関心ってのはあると思うんだけど、
18:39 つけたりはずしたりはあんまりしないここ数年です

なるほど。 prototype チェーンのダイナミックさは AOP という関心ごと一個のために利用するんじゃなくて、 AOP で一個、 trait で一個と関心ごとを後から後からくっつけられることに使った方が有意義そうだ。上の実用クラス定義で言えば…。

/**
 * Test Aspected klass
 */
var Test = function Test() {
    this.testMethodA = function() {
    };
    this.testMethodB = function() {
    };
    this.testMethodC = function() {
    };
}.aspected().trait(HTMLOutputTrait, SpecificStringOperationTrait);

みたいな感じか。 trait も最終具象オブジェクトにミックスインしちゃうんじゃなくて prototype チェーンのどっかにぶちこんじゃうのが ECMA3 的っぽい。キレイに外せるし。外せるか!?。チェーン上の上下関係を trait の依存関係に投射できるな。

でも AOP もう流行ってないですよ。 DI じゃないすかね。

trait については次回。