メソッドをプライベート化する実験

私が prototype.js に基づいた OO プログラミングをしていて度々思うのが、メソッドをプライベートにする方法が無いものだろうか、ということです。

オーソドックスな、function 文を使用するクラス定義方法であれば、以下の様に関数を "this" と関連付けないことで、プライベートなメソッド (と言うか関数) とすることができます:

function Klass()
{
    this.publid_method = function ()
    {
    };

    function private_method()
    {
    }
}

でも、Prototype に出会ってしまった今となっては、そんな時代には後戻りしたくないわけなんですね。

id:llamerada さんのこちらの記事 id:llamerada:20050903:1125720037 で、この事を実現する方法が提示されています。

いるんですが、残念なことに Mozilla 系でしか実現できないということです。これは JScript しか使わない私 (理由は、この Bloglines Notifier です) には問題です。

そこで、別のアプローチを考えてみました。

メソッドの caller (object.method() を呼んでいる関数またはグローバル領域) が誰かが分かれば良いんじゃないでしょうか。

もしそれが分かれば、対象メソッドの実行を許可して良いかどうかを判断することができると思います。

そのための仕組みとしては、オリジナルのメソッドを記憶しておき、メソッドの呼び出しを捕捉するためのフック的なものと置き換える、といったことを行わなければならないでしょう。

結果、このようになりました。例によって私の prototype.js の拡張を利用します: 特に、__class__ という特別なインスタンス変数にプロトタイプへの参照が保存されていることを利用しています。

Class.Methods =
{
    ...
    private: function ()
    {
	$A(arguments).each((function (method)
	{
	    this[method] = function ()
	    {
		// `bind' によって作られたクロージャの一つ外のスコープを得る
		var c = arguments.callee.caller.caller;
		if (c)
		    for (var m in this.__class__)
			if (c == this.__class__[m])
			    return this.__class__[method].apply(this,
								arguments);
		throw "Private method " + method + " called";
	    }.bind(this);
	}).bind(this));
    }
};

流れを説明しますと、まず、プライベートにしたいメソッドの名前を幾つでも受け取ります。

そして、動的にそれらと同じ名前でインスタンス・メソッドを定義し直します。その中身は、メソッドが呼ばれた時に、caller がそのメソッドと同じクラスに属しているかどうかをチェックする、というものです。

属していると判断されれば、プロトタイプからオリジナルのメソッドが実行されます。判断されなければ、他所から呼ばれたと見なし、例外を発生します。

なお、いったんプライベート・メソッドが実行されてからはプロトタイプのコードが使われるわけですから、そこからさらに他の、あるいは再帰的に、プライベート・メソッドを呼び出すことは通常どおりに可能です (変更したインスタンス・メソッドは呼ばれないということです)。


使い方です:

var Klass = Class.create();
Klass.prototype =
{
    initialize: function()
    {
	this.private('dangerous_method');
    },
    safe_method: function ()
    {
	this.dangerous_method();
    },
    dangerous_method: function ()
    {
	//
    }
};

var o = new Klass;
o.safe_method();      // -> OK
o.dangerous_method(); // -> 例外発生

ご覧のように、private() メソッドは初期化メソッドの中で実行されることを想定しています。このようにコーディング上の約束事を設けることは欠点と見なせるかもしれませんが、クラスの定義方法そのものは変えないという点では評価できるのではないでしょうか。


今後の検討課題としては:

  1. この実装は実質的には protected なのではないか。つまり、プロトタイプを検証しているので、親戚クラス間であれば private メソッドが呼べてしまうのでは?
  2. 上を受けて、private/protected の区別は実現可能か?
  3. 直系の親族クラス間で、同名メソッドの private/public の設定が違う場合の挙動はどうなる?

といった点を考えたいと思っています。

プライベート変数に関しては、もっと難しい問題になる気がするので先送りにします。

追記:

ふと思ったんですが、

「いったんプライベート・メソッドが実行されてからはプロトタイプのコードが使われる」
「変更したインスタンス・メソッドは呼ばれない」

という部分、なぜそんなことが断言できるのかよく分かりませんね。たまたま JScript ではそのような実装になってるだけかも知れませんし。Mozilla 系でも検証しないと。