日記帳

日記です。

アクセサメソッド定義メソッド(3)

http://d.hatena.ne.jp/sa-y/20060320#1142855348 の続きです.

attr_reader() でデフォルト値を指定できるようにしてみます.

Object.prototype.attr = function(name, initialValue)
{
	this.attr_reader(name, initialValue);
	this.attr_writer(name);
}

Object.prototype.attr_reader = function(name, initialValue)
{
	var capitalizedName = name.charAt(0).toUpperCase() + name.substring(1)
	var readerName = 'get' + capitalizedName;
	this[readerName] = new Function('return this["private ' + name + '"]')
	if(initialValue) this[name] = initialValue;
}

Object.prototype.attr_writer = function(name)
{
	var capitalizedName = name.charAt(0).toUpperCase() + name.substring(1)
	var writerName = 'set' + capitalizedName;
	this[writerName] = new Function('newValue', 'this["private ' + name + '"] = newValue')
}

使い方はこんな感じです.

var obj = new Object()
obj.attr("name", "Anonymouse")
obj.attr("age", 0)
alert(obj.getName()) // => Anonymouse
alert(obj.getAge())  // => 0
obj.setName("sa-y")
obj.setName(20)
alert(obj.getName()) // => sa-y
alert(obj.getAge())  // => 20

普通に便利っぽいですね.

ここまでやったらまたデフォルト値として関数オブジェクトを渡して遅延初期化機能付きアクセサメソッド定義*1をやりたくなるのが人情ってものですよね.全国12万4千人の遅延初期化愛好会会員の人達もそれを望んでいるはずですし.

Object.prototype.attr_reader = function(name, initializer)
{
	var capitalizedName = name.charAt(0).toUpperCase() + name.substring(1)
	var readerName = 'get' + capitalizedName;
	var propertyName = 'private ' + name;
	var fastReader = new Function('return this["' + propertyName + '"]')
	if(initializer instanceof Function)
	{
		this[readerName] = function()
		{
			if(!this[propertyName])
			{
				this[propertyName] = initializer.call(this);
				this[readerName] = fastReader
			}
			return this[propertyName];
		}
	}
	else
	{
		this[readerName] = fastReader;
		this[propertyName] = initializer;
	}
}

なんだか Ruby で書いた時よりずいぶんと楽ですね. Function オブジェクトをプロパティに設定するだけでメソッドの定義ができるというお手軽さのおかげでしょうか?

var obj = new Object();
obj.attr("FirstAccessDate", function(){ return new Date() })
alert(obj.getFirstAccessDate()); // アクセスした時間の Date オブジェクト
alert(obj.getFirstAccessDate()); // 2回目以降は上と同じ Date オブジェクト

HTML の DOM オブジェクトの取得とかに使うと便利かもしれません.

<script>
var obj = new Object();
obj.attr("NameElement", function(){ return document.getElementById("name") })
obj.getName = function(){ return this.getNameElement().value }
...
</script>
<body>
...
<input type="text" id="name" />
<input type="button" onclick="alert(obj.getName())" />
...
</body>

こうすると body 要素の onload イベントハンドラとかで初期化する必要がなくなります.さらにイベントハンドラから利用するようなプロパティの場合にはイベントが起らない限り初期化コードが呼ばれなくてすむので効率的(貧乏性)ですし.

さらに Function コンストラクタを使って書き直してみます.

Object.prototype.attr_reader = function(name, initializer)
{
	var capitalizedName = name.charAt(0).toUpperCase() + name.substring(1)
	var readerName = 'get' + capitalizedName;
	var propertyName = 'private ' + name;
	var fastReader = new Function('return this["' + propertyName + '"]')
	if(initializer instanceof Function)
	{
		var readerString = 
			'if(!this["' + propertyName + '"])' +
			'{' +
			'	initializer = arguments.callee.initializer;' +
			'	this["' + propertyName + '"] = initializer.call(this);' +
			'	this["' + readerName + '"] = arguments.callee.fastReader;' +
			'}' +
			'return this["' + propertyName + '"];'
		var reader = new Function(readerString)
		reader.initializer = initializer;
		reader.fastReader = fastReader;
		this[readerName] = reader;
	}
	else
	{
		this[readerName] = fastReader;
		this[propertyName] = initializer;
	}
}

とりあえず Function オブジェクト自体のプロパティに差し替え用の Function オブジェクトと初期化処理の Function オブジェクトを渡して arguments.callee 経由で取り出してるあたりがポイントでしょうか?

しかし随分と読み難くなってしまいました.ここまで可読性が下るようなら却下かなぁ…