開発‎ > ‎言語‎ > ‎JavaScript‎ > ‎

JavaScript で OOP (frozen)

せめてこれくらいできないと、Strategy パターンとか書けませんので。

というわけで、JavaScript でクラス的な発想の継承を行うための書き方について考えていました。条件として:
  • あまり冗長にならないようにしたい
  • 分かりづらくならないようにしたい
  • 継承元の「クラス名」を、継承先で一度しか書きたくない
  • 継承元のメソッド呼び出しに、継承元の「クラス名」を指定したくない
といったところです。

// 最初の「クラス」。これは普通

function Foo () {
  this.inheritance_depth = 1;
}
Foo.prototype.get_name = function() {
  return ("Foo");
};
Foo.prototype.get_inheritance_stack = function() {
  return (["Foo"]);
};

// 2 段目までの、簡易な継承。これも比較的普通

with (Bar = function () {
  this.Super();
  this.inheritance_depth ++;
}) {
  Super = Foo;
  prototype = Object.create(Super.prototype);
  prototype.Super = Super; // ← 継承階層内で一度しか使えない
  prototype.get_name = function() {
    return ("Bar");
  };
  prototype.get_inheritance_stack = function() {
    return (this.Super.prototype.get_inheritance_stack.apply(this).
     concat("Bar") )
  };
}

// 3 段目以上の継承 (Object を基底に 1 段目からこの形でも構わない)

Baz = (function () {
  // 基底クラス
  var Super = Bar;
  // コンストラクタ
  var Class = function () {
    // コンストラクタ・チェーン
    Super.apply(this);
    this.inheritance_depth ++;
  }
  // プロトタイプのコピー
  Class.prototype = Object.create(Super.prototype);
  // オーバーライドされるメソッド
  Class.prototype.get_name = function() {
    return ("Baz");
  };
  // 継承元への連鎖呼び出しは、クロージャへ基底クラスをバインドして
  Class.prototype.get_inheritance_stack = function() {
    return (Super.prototype.get_inheritance_stack.apply(this).
     concat("Baz") );
  };
  return Class;
})();

// -------------------------------------------------------------------

baz = new Baz();
alert(
 "Name: " +  baz.get_name() + " | " +        // → "Baz"
 "Depth: " + baz.inheritance_depth + " | " + // → 3
 "Stack: " + baz.get_inheritance_stack() );  // → ["Foo", "Bar", "Baz"]

baz.constructor  が "Class" になる。なぜだろう?

コンストラクタのチェーンやメソッドのチェーンが不要、あるいは少々冗長な書き方を許容できるのであれば、話は簡単なんですけれどもね。

function Foo () {
  this.inheritance_depth = 1;
}
Foo.prototype.get_name = function() {
  return ("Foo");
}
Foo.prototype.get_inheritance_stack = function() {
  return (["Foo"]);
}

function Bar () {
  Foo.apply(this);
  this.inheritance_depth ++;
}
Bar.prototype = Object.create(Foo.prototype);
Bar.prototype.constructor = Bar;
Bar.prototype.get_name = function() {
  return ("Bar");
}
Bar.prototype.get_inheritance_stack = function() {
  return (Foo.prototype.get_inheritance_stack.apply(this).
   concat("Bar") );
}

こう冗長だと、リファクタリング支援のツールは要りそうだな。

あるいは、もっとノホホンとダックタイピングしていれば良いのか。

Foo → Bar 継承



参考:
Object.create() は、ECMA の 5 から。「new 方式」のデメリットは? → 継承の際に基底クラスのコンストラクタが走った結果の「インスタンス」(オブジェクト?)をプロトタイプとしてしまうので、その時点で余計なインスタンスの設定等の副作用が出てしまうこと。なので、純粋にプロトタイプだけをチェーンした、空のオブジェクトが欲しいのです。

"prototype" は、「クラス」(コンストラクタ)のプロパティであり、そこから生成された「インスタンス」の "__proto__" プロパティが指す先となる。よって、実際にチェーンするのは「prototype」ではなく「__proto__」。

「__proto__」を使っても構わなければ継承先のコード内に継承元の名前は出なくなるのでスッキリするのですが、実装依存してしまいますね。ECMA 6 から入るようです。 ∥ DailyJS: JS101: __proto__ http://dailyjs.com/2012/11/26/js101-proto/ , ECMA の 5 は 2009 年、ECMA の 6 は策定中。 ∥ ECMAScript - Wikipedia

function Foo () {
  this.inheritance_depth = 1;
}
Foo.prototype.get_name = function() {
  return ("Foo");
}
Foo.prototype.get_inheritance_stack = function() {
  return (["Foo"]);
}

function Bar () {
  Bar.prototype.__proto__.constructor.apply(this);
  this.inheritance_depth ++;
}
Bar.prototype = Object.create(Foo.prototype);
Bar.prototype.constructor = Bar;
Bar.prototype.get_name = function() {
  return ("Bar");
}
Bar.prototype.get_inheritance_stack = function() {
  return (Bar.prototype.__proto__.get_inheritance_stack.apply(this).
   concat("Bar") );
}

function Baz () {
  Baz.prototype.__proto__.constructor.apply(this);
  this.inheritance_depth ++;
}
Baz.prototype = Object.create(Bar.prototype);
Baz.prototype.constructor = Baz;
Baz.prototype.get_name = function() {
  return ("Baz");
}
Baz.prototype.get_inheritance_stack = function() {
  return (Baz.prototype.__proto__.get_inheritance_stack.apply(this).
   concat("Baz") );
}

baz = new Baz();
baz.get_name();
baz.inheritance_depth;
baz.get_inheritance_stack();

あとは mixin か。


Comments