檜山正幸のキマイラ飼育記 このページをアンテナに追加 RSSフィード Twitter

キマイラ・サイトは http://www.chimaira.org/です。
トラックバック/コメントは日付を気にせずにどうぞ。
連絡は hiyama{at}chimaira{dot}org へ。
蒸し返し歓迎!
このブログの更新は、Twitterアカウント @m_hiyama で通知されます。
Follow @m_hiyama
ところで、アーカイブってけっこう便利ですよ。

2005-10-18 (火)

プログラマのためのJavaScript (号外):こんな継承はどう?

| 12:10 | プログラマのためのJavaScript (号外):こんな継承はどう?を含むブックマーク

昨日の「プログラマのためのJavaScript (11):継承についてもう少し」に、いくつかのコメントをいただきました。そのなかで、nanto_viさんに素晴らしいヒントを提供していただいたので紹介し、クラス(もどき)の継承への補足とします。

まずは、nanto_viさんが教えてくれたコードを再掲:

 var Traits = function () {};
 Traits.prototype = SuperClass.prototype;
 SubClass.prototype = new Traits();

Traitsは一時的なコンストラクタですが、SuperClassのトレイツ(共通性質の定義)をそのまま持ち、余分な(おそらくは弊害を生むであろう)初期化実行コードは持たないものです。(こんなトリックがあったんだ、フーム、ムムム。)

この手続きを関数にしてみます。(ちなみに、名前extendsは予約されているからダメ。)

function inherits(Sub, Super) {
 // 上記と同様なコード
}

そして、この関数を inherits(Sub, Super); と呼び出してもいいのですが、Function.prototype内に定義すると、Sub.inherits(Super); と書けるので、宣言っぽくて可読性が向上しそうです。

何度も使っている例、ColoredPoint extends Point をこの方法を使って書き直してみました。太字部分が継承実行コード(もちろん、本物の宣言ではない)ですが、随分みやすくていい感じだと僕は思うのですが、いかがでしょう。

Function.prototype.inherits = function(Super, copyStatic_, defineSuper_) {
 // オプション引数 copyStatic_ :静的メンバーをコピーする
 // オプション引数 defineSuper_:_superclass, _superを定義する

 var Traits = function(){};
 Traits.prototype = Super.prototype;
 this.prototype = new Traits();
 this.prototype.constructor = this;
 if (copyStatic_) {
  for (var p in Super) {
   if (typeof this[p] == 'undefined') {
    this[p] = Super[p]
   }
  }
 }
 if (defineSuper_) {
  this._superclass = Super;
  this._super = Super.prototype;
 }
}
   
/* 
 * ========== クラスPoint ========== *
 */

/* クラスの定数 */
Point.MAX_X = 1000;
Point.MAX_Y = 1200;

/* クラス(静的)メソッド */
Point.checkBounds = function(x, y) {
 if (Math.abs(x) > Point.MAX_X) {
  throw new Error("out of bounds - x");
 }
 if (Math.abs(y) > Point.MAX_Y) {
  throw new Error("out of bounds - y");
 }
}

/* コンストラクタ */
function Point(x_, y_) {
 if (typeof x_ == 'undefined') x_ = 0;
 if (typeof y_ == 'undefined') y_ = 0;
 Point.checkBounds(x_, y_);
 /* インスタンス初期化コード */
 this.x = x_;
 this.y = y_;
}

/* インスタンスメソッド */
Point.prototype.moveTo = function(newX, newY) {
 Point.checkBounds(newX, newY);
 this.x = newX;
 this.y = newY;
}
/* インスタンスメソッド(オーバライド) */
Point.prototype.toString = function() {
 return "(" + this.x + ", " + this.y + ")";
}

/* 
 * ===== サブクラスColoredPoint ===== *
 */

/* 継承の宣言 */
ColoredPoint.inherits(Point, 'copyStatic', 'defineSuper');

/* 追加の定数 */
ColoredPoint.BLACK = 0;
 // ... 色の名前が並ぶ

/* このクラスのコンストラクタ */
function ColoredPoint(color_, x_, y_) {
 if (typeof color_ == 'undefined') color_ = ColoredPoint.BLACK;
 // superconstructor call
 ColoredPoint._superclass.apply(this, [x_, y_]);
 this.color = color_;
}

/* メソッドのオーライド */
ColoredPoint.prototype.toString = function() {
 // super method call
 var s = ColoredPoint._super.toString.apply(this, []);
 return s + "/" + this.color;
}

再度: nanto_viさん、どうもありがごうございました。