C#版のコードを以下に示す。コードをシンプルにするために、メソッドの定義、デフォルト引数の指定などは行っていない。
using System;
namespace cs
{
public class BASE
{
public string bprop; // BASEクラスのインスタンス変数
public BASE(string bprop)
{
this.bprop = bprop;
}
}
public class DERIVED : BASE
{
public string dprop; // DERIVEDクラスのインスタンス変数
public DERIVED(string bprop, string dprop) : base(bprop)
{ // ↑ base()呼び出しで基底クラスのメンバーを初期化
this.dprop = dprop;
}
}
public class Program
{
public static void Main(string[] args)
{
var b = new BASE("base1");
var d = new DERIVED("base2", "derived");
Console.WriteLine(b.bprop);
Console.WriteLine(d.bprop);
Console.WriteLine(d.dprop);
}
}
}
このコードも特に説明の必要はないだろう。BASEクラスが基底クラス、DERIVEDクラスが派生クラスで、Mainメソッドでは両者のオブジェクトを生成して、それぞれのメソッドを呼び出しているだけだ。
例によってJavaScript 5での記述例を見てみよう。
var BASE = (function() {
function BASE(bprop) {
this.bprop = bprop;
}
return BASE;
})();
var DERIVED = (function() {
function DERIVED(bprop, dprop) {
BASE.call(this, bprop); // 基底クラスのコンストラクター呼び出し
this.dprop = dprop;
}
return DERIVED;
})();
// 継承関係の確立: DERIVED.prototypeのプロトタイプをBASE.prototypeに設定
Object.setPrototypeOf(DERIVED.prototype, BASE.prototype);
var b = new BASE("base1");
var d = new DERIVED("base2", "derived");
console.log(b.bprop);
console.log(d.bprop);
console.log(d.dprop);
まず、C#版のコードでは、派生クラスのコンストラクターを定義する際に、基底クラスのメンバーを初期化するために「public DERIVED(string bprop, string dprop) : base(bprop)」として基底クラスのコンストラクターを呼び出していた。これに対して、JavaScript 5版のコードではコンストラクター内部で「BASE.call(this, BASEコンストラクターに渡す引数)」(または「BASE.apply(this, BASEコンストラクターに渡す引数)」)という形で呼び出すことにも注意しておこう。
また、C#では上のコードのように明示的にコンストラクターを呼び出さない場合には、最初に基底クラスの無引数のコンストラクターが呼び出されるが、JavaScript 5で上記のようなコードを書いた場合、常に自分でコンストラクター呼び出しを行う必要がある(JavaScript 5にはクラス機構が用意されていてそうした処理を自動的に行ってくれるわけではなく、クラスの継承に相当する処理を自力で書いているだけだからだ)。
次にC#ではクラスを継承する際には「class 派生クラス名 : 既定クラス名」という記述をするが、JavaScript 5では先ほど出てきたプロトタイプのすげ替えを行うことで、継承に相当する処理を行う。これを行っているのが「Object.setPrototypeOf(DERIVED.prototype, BASE.prototype);」という行だ。これはDERIVEDコンストラクターのプロトタイプのプロトタイプ(DERIVED.prototype.__proto__プロパティ)を、コンストラクターBASEのプロトタイプにしている。
BASEとDERIVEDの二つのコンストラクター、それらを利用して作成されるオブジェクト、プロトタイプの関係を以下に示す。
図を見ると分かるように、DERIVED.prototypeのプロトタイプをBASE.prototypeが参照するオブジェクトにすることで、「BASEが親でDERIVEDが子」という継承関係が確立される。このようにJavaScriptではプロトタイプをチェーンさせることで、クラスの継承階層をさかのぼりながらプロパティやメソッドの検索を行うようになっている(ただし、この例ではインスタンスメソッドなどは定義していないので、図には現れていない)。このこと自体はECMAScript 2015でも変わりないし、TypeScriptでもそうした操作はもちろん可能だ。
では、TypeScriptとECMAScript 2015でクラス継承がどのように記述できるかを見てみよう。まずはTypeScriptからだ。
class BASE {
bprop:string; // インスタンスのプロパティの宣言
constructor(bprop:string) {
this.bprop = bprop;
}
}
class DERIVED extends BASE { // 継承を行うにはextendsキーワードを使用する
dprop:string; // インスタンスのプロパティの宣言
constructor(bprop:string, dprop:string) {
super(bprop); // 基底クラスのコンストラクター呼び出し
this.dprop = dprop;
}
}
var b = new BASE("base1");
var d = new DERIVED("base2", "derived");
console.log(b.bprop);
console.log(d.bprop);
console.log(d.dprop);
C#で派生クラスを定義するには「class 派生クラス : 基底クラス」と派生クラスと基底クラスで「:」をサンドイッチするような形で継承関係を記述していたが、TypeScriptではextendsキーワードを使用する。
また、コンストラクターで基底クラスのコンストラクターを呼び出す方法もC#とは異なっている。TypeScriptではコンストラクター内部で「super」メソッドを呼び出して、基底クラスのインスタンスの初期化に必要な情報を引き渡す(TypeScriptでもECMAScript 2015でもsuperメソッド呼び出しは必須である)。
JavaScript 5版のコードとは異なり、Object.setPrototypeOfメソッドを明示的に呼び出す必要はないことに注意しよう。継承にまつわる処理を隠ぺいしてくれるのがTypeScriptのよいところだ(ただし、コンパイル後のコードを見ると、予想以上に複雑な処理をしている。興味のある方は調べてみてほしい)。
ECMAScript 2015のコードは、例によって、TypeScriptバージョンとほぼ同様だ。
class BASE {
constructor(bprop) {
this.bprop = bprop;
}
}
class DERIVED extends BASE {
constructor(bprop, dprop) {
super(bprop); // 基底クラスのコンストラクター呼び出し
this.dprop = dprop; // プロパティの初期化(宣言がないことに注意)
}
}
var b = new BASE("base1");
var d = new DERIVED("base2", "derived");
console.log(b.bprop);
console.log(d.bprop);
console.log(d.dprop);
ここまでに見てきたように、クラスベースのオブジェクト指向プログラミングに慣れている開発者にとっては、TypeScriptやECMAScript 2015は極めてとっつきやすい言語だといえる。
今回はC#/JavaScript 5/TypeScript/ECMAScript 2015でクラス定義がどんなものになるかを比較した。とはいえ、本来の趣旨ではなさそうなJavaScriptコードの説明が増えたのは、TypeScriptやECMAScript 2015では表面的にはクラスを使うことですっきりと分かりやすいコードが記述できるが、内部的にはそういうことをしているのだということを知ることがJavaScript、プロトタイプベースのオブジェクト指向プログラミングを理解する上でも重要だからだ。
次回以降は、今回説明しきれなかったECMAScript 2015/TypeScriptが持つさまざまな要素について、C#とJavaScriptのコードを比較しながら紹介していきたい。
Copyright© Digital Advantage Corp. All Rights Reserved.