鶏肋(けいろく)。文字通りに取れば、鶏の肋骨という意味。
三国志演義という「フィクション」の中で、曹操は悪役として登場します。多くの人は演義を多少なりとも知っているものですが、正史は読まないので、曹操に悪いイメージを持っていることでしょう。中国史上、これほど不当に扱われている英雄も珍しいのではないでしょうか?
曹操は、すぐれた政治家であり、孫子を編纂したことからも分るように戦略家かつ文章家でした。また、三国志の時代きっての詩家であり、彼の読んだ短歌行は僕のお気に入りの漢詩です。
曹操は、劉備の漢を攻めあぐんでいるときに、鶏肋とつぶやきました。部下の誰もが意味が分からずにいる中、楊修だけが撤退の準備を始めました。楊修曰く:
「鶏の肋骨は、これを喰らうに肉なく、これを捨てんとするも捨てがたき味あり」
つまり、漢は捨てるのは惜しいが、たとえ攻略できても役に立たないということで、曹操が撤退の意思を固めているのを読み取ったのです。
これから、「鶏肋」は「取っておいても役には立たないが、捨てるにはおしいもの」を意味するようになりました。
前置きが長くなりました。今日は、鶏肋と表現するのがぴったりの話題です。
プライベートな変数の実現
最近のオブジェクト指向では、インスタンス変数はプライベートにしておいて、オブジェクト外からアクセスするときは、いわゆる 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"); }); }