あどけない話

Internet technologies

JavaScript で setter/getter

鶏肋(けいろく)。文字通りに取れば、鶏の肋骨という意味。

三国志演義という「フィクション」の中で、曹操は悪役として登場します。多くの人は演義を多少なりとも知っているものですが、正史は読まないので、曹操に悪いイメージを持っていることでしょう。中国史上、これほど不当に扱われている英雄も珍しいのではないでしょうか?

曹操は、すぐれた政治家であり、孫子を編纂したことからも分るように戦略家かつ文章家でした。また、三国志の時代きっての詩家であり、彼の読んだ短歌行は僕のお気に入りの漢詩です。

曹操は、劉備の漢を攻めあぐんでいるときに、鶏肋とつぶやきました。部下の誰もが意味が分からずにいる中、楊修だけが撤退の準備を始めました。楊修曰く:

「鶏の肋骨は、これを喰らうに肉なく、これを捨てんとするも捨てがたき味あり」

つまり、漢は捨てるのは惜しいが、たとえ攻略できても役に立たないということで、曹操が撤退の意思を固めているのを読み取ったのです。

これから、「鶏肋」は「取っておいても役には立たないが、捨てるにはおしいもの」を意味するようになりました。

前置きが長くなりました。今日は、鶏肋と表現するのがぴったりの話題です。

プライベートな変数の実現

最近のオブジェクト指向では、インスタンス変数はプライベートにしておいて、オブジェクト外からアクセスするときは、いわゆる getter/setter を使うのが流儀となっています。

理由が分らない人は、「オブジェクト指向プログラムでgetter/setterメソッドを使わなければならない10の理由」を読むといいでしょう。

getter/setter を JavaScript で実現するために、以下のようなコンストラクタを書きます。

function Foo(x) {
    var _x = x;
    this.getX = function() {return _x};
    this.setX = function(x) {_x = x};
}

this._x ではなく、var _x としてあるところがミソです。変数 _x には、Foo の中で提議してある関数 getX/setX からしかアクセスできません。

var foo = new Foo(1);
foo.getX() // = > 1

しかし、foo._x として無理矢理アクセスしてみると、foo の「新たなプロパティ(変数)」ができてしまいます。

foo._x = 2
foo._x // => 2
foo.getX() // => 1

このようなアクセスのときに、エラーを発生させる方法を考えたいと思いました。

JavaScript でいう setter/getter

紛らわしいのですが、JavaScript にも setter/getter という用語があり、オブジェクト指向の setter/getter とは意味が違います。

JavaScript の setter/getter は、関数に対して変数のようなアクセスを許す仕組みです。つまり、ある変数にアクセスすると、裏では関数が動きます。これを使って、エラーを発生させてみましょう。

function Foo(x) {
    this.initialize(x);
}

Foo.prototype = {
    initialize: function (x) {
	var _x = x;
	this.getX = function() {return _x};
	this.setX = function(x) {_x = x};
    },
    get _x() { throw new Error("use getter"); },
    set _x() { throw new Error("use setter"); }
}

var foo = new Foo(1);
foo.getX() // => 1
foo._x // Error 発生 use getter
foo._x = 2; // Error 発生 use setter
foo.setX(3)
foo.getX() // => 3

この方法の問題点は、ブラウザ依存なことです。Firefox には get/set がありますが、その他のブラウザで実装されているとは限りません。

クロス・ブラウザにする

amachang さんに教えて頂いて、クロス・ブラウザにしてみました。set/get の代わりに、"__defineGetter__"/"__defineSetter__" を使います。

Foo.prototype = {
   initialize: function (x) {
       var _x = x;
       this.getX = function() {return _x};
       this.setX = function(x) {_x = x};
   }
}

if ("__defineGetter__" in Foo.prototype) {
  Foo.prototype.__defineGetter__("_x", function() { throw new Error("use getter"); });
}


if ("__defineSetter__" in Foo.prototype) {
  Foo.prototype.__defineSetter__("_x", function() { throw new Error("use setter"); });
}

やっぱり鶏肋

ここまで考えたにも関わらず、この方法を使うのは止めました。なぜなら、クラス内からも getter/setter を使わないと変数にアクセスできないからです。

オブジェクト指向の getter/setter には二つの流派があるそうです。

  • クラス内外両方から必ず getter/setter を使う
  • クラス内からは直接変数にアクセスし、クラス外からは getter/setter を使う

僕は後者の流派ですから、考えた方法は受け入れがたいのです。。。orz