ちょっと高度にJavaScript/クロージャでアクセサを作る

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

前回はクロージャの基礎を解説しましたが、あれだけではイマイチなんの役に立つのかイメージが湧かないかもしれません。そこで今日は、もっと実用的な例を挙げてみようと思います。

private変数的なもの+アクセサを作る

JavaScriptでクラス(的なもの)を作ろうとするとまず気になるのが、プロパティが全部publicになってしまうことです。JAVA等の言語の経験があれば、private変数が作れないというのがとても不便に感じると思います。どこからでも自由に値を変更できてしまうので、正常な動作を担保するのが難しいですね。

しかしクロージャを使えば、レキシカル変数の保持されつつも外部からアクセス出来ないという特性を利用して、クラス外部から保護された領域を作ることができます。

まず、普通にクラスを作ってみます。

function Multiplier(value1, value2) {
	this.value1 = value1;
	this.value2 = value2;
	if(!this.validate(value1) || !this.validate(value2)) {
		throw "not a number!";
	}
}
Multiplier.prototype.validate = function(value) {
	if(isNaN(value)) {
		return false;
	}
	return true;
}
Multiplier.prototype.calculate = function() {
	return this.value1 * this.value2;
}

var m = new Multiplier(100, 100);
console.log(m.calculate()); // 10000

コンストラクタで二つの引数を受け取って、メソッドcalculateで掛け算して返すクラスです。コンストラクタで、引数が数値であるかどうかをチェックしています。問題なく機能するようです。しかし・・・

var m = new Multiplier(100, 100);

m.value1 = "hogehoge";

console.log(m.calculate()); // NaN

外からvalue1を強制的に書き換えてみました。計算結果がNaNになってしまいます。 では、クロージャを使って、間違った使い方ができないように修正してみます。

function Multiplier(value1, value2) {
	this.setValue1(value1);
	this.setValue2(value2);
}
//value1のsetter
Multiplier.prototype.setValue1 = function(value) {
	if(!this.validate(value)) {
		throw "value1 is not a number!";
	}
	//value1のgetter
	this.getValue1 = function() {
		return value;
	}
}
//value2のsetter
Multiplier.prototype.setValue2 = function(value) {
	if(!this.validate(value)) {
		throw "value2 is not a number!";
	}
	//value2のgetter
	this.getValue2 = function() {
		return value;
	}
}
Multiplier.prototype.validate = function(value) {
	if(isNaN(value)) {
		return false;
	}
	return true;
}
Multiplier.prototype.calculate = function() {
	return this.getValue1() * this.getValue2();
}

var m = new Multiplier(100, 100);

console.log(m.value1)// undefined
console.log(m.value2)// undefined
console.log(m.getValue1())// 100
console.log(m.getValue2())// 100
console.log(m.calculate()); // 10000

m.setValue1(200);
m.setValue2(200);
console.log(m.calculate()); // 40000

m.setValue1("hogehoge"); // uncaught exception: value1 is not a number!

value1とvalue2を隠蔽する事ができました。さらに、getValue2,getValue2のgetterアクセサメソッドで値を参照できます。また、setValue2,setValue2のsetterアクセサメソッドで不正な値が設定されることを防ぐことができます。この例では、setterがエンクロージャ、getterがクロージャです。

まとめ

前回、「return function」とあったら~と書きましたが、今回の例には出て来ませんでした。こういう場合もあるということで・・・。あと1回、また違った切り口の実用例を掲載する予定です。次こそは「return function」しますので何卒ご容赦を。