オブジェクト指向プログラミング (OOP: Object-Oriented Programming) とは、オブジェクトと呼ばれるデータ構造体の定義をベースとしたソフトウェア開発パラダイムのことです。オブジェクトを構成するのはデータ・プロパティーと関数で、これらのプロパティー (メンバー変数) と関数 (またはメソッド) が、ソフトウェアがそのオブジェクトで実行できる操作を定義します。OOP の主な利点は、コードを編成しやすくすることによって、コードの再利用および保守を容易にすることです。
OOP の大前提は、開発者が独自に開発するソフトウェアの中でオブジェクトを作成し、そのオブジェクトに適用する一連のプロパティーと、これらのプロパティーを取得または変更するための一連のメソッドまたは関数を定義することです。例えばオブジェクトの単純な例として car
(自動車) を引用すると、car
に関連付けるデータ・プロパティーには、manufacturer
(メーカー)、model number
(型式番号)、registration number
(登録番号)、color
(色)、cubic capacity
(容積) などが考えられます。そして、car
オブジェクトのメソッドとしては、accelerate
(加速)、brake
(ブレーキ)、change gear
(ギア・チェンジ)、turn
(進行方向変更)、stop
(停止) などがあります。OOP での考え方は、開発者がすべての自動車に共通する基本的なプロパティーとメソッドを定義し、個々の自動車がその定義の形に沿って、それぞれに違う値を使用するというものです。この記事で後から説明しますが、ソフトウェア開発でのオブジェクト指向には、さまざまな手法があります。
読者がこの記事を最大限に活用するには、少なくともオブジェクト指向プログラミングとその概念を十分に理解している必要があります。以下に、オブジェクト指向の開発を話題にする際に共通して使用する用語について簡単に説明しておきます。なお、すべてのタイプの OOP が以下に説明する概念のすべてを採り入れているわけではありません。例えば、JavaScript などのプロトタイプ・ベースのオブジェクト言語にはクラスの概念がありません。
クラス・ベースのオブジェクト指向開発では、クラスが、オブジェクトを構成するさまざまなプロパティーおよび関数を定義します。つまり、クラスはオブジェクトを生成するためのテンプレートを定義するということです。したがってクラスが定義する属性とアクションは、オブジェクトが共通して従うことのできるものでなければなりません。クラスは一般に、メンバー変数とメソッドで構成されます。
オブジェクトのメンバー変数は、そのオブジェクトの属性あるいはプロパティーです。前に引用した自動車の例で言うと、このオブジェクトの属性には manufacturer
、model
、color
、cubic capacity
などがあります。
メソッドとは、オブジェクトが実行できるアクションのことです。例えば、自動車は加速したり (accelerate
)、ブレーキをかけたり (brake
)、方向転換したり (turn
) するなどのアクションを実行することができます。大抵の場合、メソッドはメンバー変数の値を変更することになります。例えば、car
オブジェクトが accelerate
メソッドを使用して加速すると、現行速度のプロパティーの値が増えます。多くのオブジェクトには、オブジェクトが作成されると同時に必ず呼び出される、コンストラクターと呼ばれる特殊なメソッドがあります。
インスタンス、またはオブジェクトは、オブジェクトを定義するためのテンプレートではなく、実際のオブジェクトそのものです。一例として、car テンプレートのプロパティーとメソッドを持つ、myCar
というオブジェクトがあるとします。オブジェクトのインスタンスでは、プロパティーに実際に値が設定されます。例えば myCar
の color プロパティーには silver
の値、cubic capacity プロパティーには2500
の値が設定されるなどです。オブジェクトの現行のプロパティー値は、オブジェクトの状態と呼ばれます。状態は、オブジェクトのライフサイクル全体を通して変わる可能性があります。
クラス・ベースの OOP での継承とは、サブクラスまたは子クラスが、そのスーパークラスまたは親クラスのメンバー変数およびメソッドを継承するプロセスのことです。サブクラスは親クラスの属性およびアクションを継承するだけでなく、独自のメンバー変数とメソッドを定義したり、親クラスのプロパティーにデフォルト値を設定したりすることもできます。例えば、Car
クラスのサブクラスとして、FourByFour
というクラスがあるとします。このサブクラスは、そのスーパークラスの drivetrain 属性をデフォルトで 4WD
(四輪駆動) に設定することができます。さらに、四輪駆動車だけに適用される transfer case という名前の別の属性を定義することや、通常の自動車にはない低速ギアを変更するためのメソッドを定義することもできます。
クラス・ベースの OOP では、メンバー変数が private として宣言されることがよくあります。これは、クラスの制約外ではメンバー変数にアクセスしたり、メンバー変数を変更したりできないようにするための宣言です。クラス内でこのような private メンバー関数の値を取得、または変更するメソッドを定義するために使える、修飾子という特殊なメソッドがあります。プログラマーはこれらのメソッド (ゲッター、セッターとも呼ばれます) を使って、アプリケーションや他のクラスが特定のプロパティーにしかアクセスできないように、情報を隠すことができます。この手法は通称、カプセル化と呼ばれています。
抽象化とは、オブジェクトの現在のコンテキストでのみ重要なプロパティーおよびメソッドだけを定義するようにして、オブジェクトの複雑さを軽減するプロセスのことです。例えば、Car
クラスを定義する際に、このクラスをさらに抽象化した Vehicle
というクラスに、乗用車と他のタイプの車両 (バン、トラック、オートバイなど) に共通するすべてのプロパティーを定義するとします。この場合、Car
クラスだけでなく、Motorcycle
クラスや Van
クラスも共通して、Vehicle
クラスからプロパティーを継承することができます。
OOP のコンテキストで言うポリモーフィズムとは、サブクラスがそのスーパークラスからメソッドを継承できることを意味します。したがって、同じメソッドを再び実装する必要はありません。例えば、Car
クラスに 2 つのサブクラスがあるとします。1 つはオートマチック車用のサブクラスで (ここでは、ATCar
と名付けます)、もう 1 つはマニュアル車用のサブクラスです (MTCar
とします)。すべてのCar
オブジェクトは加速できるため、ATCar
と MTCar
はいずれも親クラスから accelerate
メソッドを継承します。ただし、ATCar
の場合、エンジンが特定の RPM レベル (回転数) に達すると、ギアを変更するメソッドを accelerate
メソッドが自動的に呼び出します。そのため、ATCar
サブクラスでは親クラスの accelerate メソッド定義をオーバーライドしますが、MTCar
サブクラスでは Car
クラスから継承するメソッドをそのまま使用します。
この記事の目的は、オブジェクト指向プログラミングについて詳説することではありません。上記で概説した概念をよく理解できない場合は、記事を読み進める前に「参考文献」で紹介している OOP に関する資料を読んでおくと参考になります。
前のセクションで紹介した概念の多くは、クラス・ベースのオブジェクト指向プログラミングと呼ばれる、特定の OOP パラダイムに特有のものです。プログラミング言語のすべてがすべて、このパラダイムに従うわけではありません。よく使用される OOP には、プロトタイプ・ベースのオブジェクト指向プログラミングというタイプもあります。これが、JavaScript 言語で使用されているパラダイムです。このセクションでは、JavaScript のオブジェクト指向の実装について簡単に説明し、この実装の使用例を紹介します。その後、クラス・ベースの OOP を経験してきた開発者が特につまずきがちな点を取り上げます。
JavaScript は単なる基本的なスクリプト言語ではありません
最初に評判になった頃の JavaScript は、主に基本的な Web ページで単純な芸を披露する手段として使用されていました。その当時 JavaScript を使用していた人々の大多数は、ソフトウェア開発者ではなく、グラフィック・デザイナーや Web デザイナーでした。彼らには HTML の経験は豊富にあっても、プログラミング言語に関する知識はほとんど、あるいはまったくありませんでした。しかし HTML だけで動的な効果を生み出すにはかなり限りがあります。そこで登場したのが JavaScript でしたが、ほとんどのデザイナーは実際に JavaScript でプログラミングする方法を習得するというよりは、必要なコードのスニペットを見つけ出してきて、特定の用途に合わせて調整できるだけの方法を学び、その小さなコードを使うというのが通常でした。デザイナーがこのプロセスを何度か踏めば、JavaScript を使えるだけの知識を得たような気になってきます。
インターネットが普及し始めた頃は、JavaScript でできることはかなり限定されていましたが、今では成熟した本格的なプログラミング言語にまで成長しています。JavaScript はもはや、Web サイト向けの単純な技を作成するためだけに使用されているのではありません。その力は、リッチ・インターネット・アプリケーション全体に及んでいます。実際のところ、言語としての JavaScript は、今までとは違う多種多様な方法でも使用されています。その一例として、CouchDB ドキュメント指向データベース管理システムでは、データベース内のデータにクエリーを実行する手段として JavaScript 関数を使用しています。
JavaScript を軽く見て、基本的なスクリプト言語だと思っている Java 開発者は少なくありません (これは、Java 開発者だけでなく、Java 以外の従来から使われているプログラミング言語の開発者にも言えることです)。JavaScript は非常に強力に成長したものの、Web サイトで芸を披露するためだけに使用するものだという考え方は未だに残っています。それは、JavaScript を使用する Web 開発者のほとんどは、JavaScript を作成する際の面倒な部分を jQuery、Prototype、Dojo などのライブラリーやフレームワークに任せているからです。実際、jQuery アプリケーションを作成するという点ではエキスパートとして見なすことができても、JavaScript 自体についての専門知識がほとんどない Web 開発者はたくさんいます。そのような開発者の多くが見逃しているのは、ありきたりの JavaScript が実際には極めて強力であり、すぐに使用できるオブジェクト指向の機能を揃えているという点です。このセクションでは、これらの機能について説明します。
JavaScript が実装するオブジェクト指向プログラミング (OOP) は、Java™ コードで使用している OOP とは別のタイプです。Java プログラミングではクラス・ベースの OOP モデルに従う一方、JavaScript での OOP ではクラスを使用しません。プロトタイプ・ベースのオブジェクト指向として知られるこの OOP モデルでは、オブジェクトを作成するためのクラス・テンプレートを定義するのではなく、それよりも単純に、必要に応じてオブジェクトを宣言します。オブジェクトが別のオブジェクトの機能を継承しなければならない場合には、プロトタイプ・オブジェクトの機能を単純に複製することができます。プロトタイプをベースとする OOP の重要な利点の 1 つは、オブジェクト・プロトタイプを実行時に変更できることです。これは、オブジェクトの構造が厳格に定義されていないことを意味します。ほとんどのクラス・ベースの OOP プログラミング言語では、実行時にクラスを動的に変更することはできません (ただし、Perl、Python、Ruby などの少数の例外はあります)。
次のセクションでは、プロトタイプ・モデルを使用した一般的な JavaScript での OOP の手法を説明します。
一般的な JavaScript での基本的なオブジェクト指向の例
Firefox の Firebug プラグインについては、この連載の第 1 回で説明しました。このコンソールを使用すれば、ファイルを編集して保存してから実行する必要も、Web ページでイベントを実行する必要もなく、JavaScript コードを実行することができます。Firefox を開いて、Firebug を起動してください。今から説明する例には特定の JavaScript ライブラリーは必要ないため、どの Web ページにアクセスしていても構いません。
JavaScript ではクラスを宣言するのではなく、オブジェクト・プロトタイプとして機能する関数を作成します。その後、キーワード new を使用して、このオブジェクトのインスタンスを作成します。例として、リスト 1 のコードを Firebug コンソールに入力してください。
リスト 1. JavaScript での基本的なオブジェクト・プロトタイプ
function Car() { } var myCar = new Car(); console.log(myCar); |
上記のコードによって、コンソール・ログに Object {}
と出力されます。
この出力をクリックすると、Firebug のウィンドウが切り替わり、そこでオブジェクトの各種プロパティーを調べることができるようになります。この例の場合、オブジェクトをクリックすると、「このオブジェクトには表示可能なプロパティーがありません。」というメッセージが表示されます。
上記のコードが実際に何を行っているかと言うと、このコードはまずオブジェクト関数 Car()
を定義し、それからnew 演算子を使ってオブジェクトをインスタンス化します。そして最後に、オブジェクトを詳しく調べられるように、オブジェクトを Firebug コンソールに出力します。このオブジェクトが世の中のためになるとは言い難いので、もう少し面白みを加えてみます (リスト 2 を参照)。
リスト 2. オブジェクトのメンバー変数の定義
function Car() { } Car.prototype.current_speed = 0; var myCar = new Car(); console.log(myCar); |
今度はコンソール・ログに、もう少し興味深い出力が表示されます。それは、Object { current_speed=0 }
という出力です。
出力をクリックすると、DOM インスペクター・ウィンドウに戻ります。このウィンドウには、current_speed
プロパティーと値 0
が表示されているはずです。Car
プロトタイプ関数を使用して作成するオブジェクトにはすべて、デフォルトで current_speed
プロパティーとデフォルト値のゼロが設定されます。
自動車が加速できなければ、ほとんど無用の代物です。そこで、今度はこのプロトタイプにメソッドを追加します (リスト 3 を参照)。
リスト 3. プロトタイプへのメソッドの追加
function Car() { } Car.prototype.current_speed = 0; Car.prototype.accelerate = function(increment) { this.current_speed += increment; } var myCar = new Car(); myCar.accelerate(30); myCar.accelerate(20); console.log(myCar); |
コンソールには、Object { current_speed=50 }
と出力されるはずです。
この出力をクリックすると、current_speed
プロパティーだけでなく、このオブジェクトで新しく使用できるようになった accelerate
メソッドも表示されます。オブジェクトのメソッドを呼び出すには、リスト 3 の例に示されているように、ドット表記object.method(arg[0], arg[1], ..., arg[N])
を使用します (この例では、myCar.accelerate(20) です)。オブジェクトの特定のプロパティーにアクセスするには、常にこの表記を使用することができます。console.log(myCar)
の行をconsole.log(myCar.current_speed)
に変更すると、コンソールの出力には実際のオブジェクト自体の表現ではなく、値 50
が表示されます。
今度は、JavaScript でコンストラクター関数を実装する方法を調べてみましょう。コンストラクターは、オブジェクトがインスタンス化された直後に呼び出される関数です。オブジェクト関数を作成すると、コンストラクター関数も作成されます。リスト 4 は、この動作の一例です。
リスト 4. コンストラクター関数本体の追加
function Car(reg_no) { this.reg_no = reg_no; console.log('Car with registration no. '+this.reg_no+' created.'); } Car.prototype.reg_no = ''; Car.prototype.current_speed = 0; Car.prototype.accelerate = function(increment) { this.current_speed += increment; } var myCar = new Car('10C500'); myCar.accelerate(30); myCar.accelerate(20); console.log(myCar.current_speed); |
リスト 4 の例では、Car
の constructor
関数が登録番号として単一の引数を受け入れます。引数を受け取った関数は、オブジェクト・インスタンスの登録番号をこの引数の値に設定し、インスタンスが作成されたことを確認するためのメッセージを Firebug コンソールに出力します。Firebug コンソールの出力ウィンドウには、リスト 5 の出力が表示されるはずです。
リスト 5. 出力
Car with registration no. undefined created. 50 |
オブジェクト・プロトタイプに継承を実装する方法を説明するために、今度はより高度な Car
プロトタイプを作成し、加速、減速、ギア・チェンジを行うためのメソッドを追加します (リスト 6 を参照)。
リスト 6. より完全な
Car
プロトタイプfunction Car(reg_no) { this.reg_no = reg_no; } Car.prototype.reg_no = ''; Car.prototype.current_speed = 0; Car.prototype.current_gear = 0; Car.prototype.accelerate = function(increment) { this.current_speed += increment; } Car.prototype.decelerate = function(decrement) { this.current_speed -= decrement; } Car.prototype.increaseGear = function() { this.current_gear++; } Car.prototype.decreaseGear = function() { this.current_gear--; } |
次に、上記の Car プロトタイプを継承し、オートマチック車を記述する ATCar
というオブジェクト・プロトタイプを作成します。この特定の例は、RPM ではなく、速度に基づくギア・チェンジを実装しているので、完璧と言うには程遠い自動車ですが、プロトタイプ・ベース OOP での継承およびポリモーフィズムの概念を理解するには参考になるはずです (リスト 7 を参照)。
リスト 7.
Car
を継承した ATCar
オブジェクト・プロトタイプfunction ATCar(reg_no) { Car.call(this, reg_no); } ATCar.prototype = new Car(); ATCar.prototype.constructor = ATCar; ATCar.prototype.accelerate = function(increment) { Car.prototype.accelerate.call(this, increment); if(increment >= 10) this.increaseGear(); } ATCar.prototype.decelerate = function(decrement) { Car.prototype.decelerate.call(this, decrement); if(this.current_speed === 0) this.current_gear = 0; else if(decrement >= 10) this.decreaseGear(); } |
上記のリストで最初に目に留まるのは、コンストラクターが Car
オブジェクト・プロトタイプで call 関数を使用しているところでしょう。基本的にこの関数は、ATCar
がインスタンス化されるたびに、親プロトタイプ定義でコンストラクターを呼び出します。これによって、確実に継承を実装することができます。この場合、ATCar
プロトタイプが Car
オブジェクト・プロトタイプの内部構造を知る必要はありません。リストでは次に、ATCar
関数の prototype プロパティーを Car
関数の新規インスタンスに設定しています。このコードは基本的に、JavaScript に対し、ATCar
プロトタイプに Car
プロトタイプのプロパティーとメソッドを継承する必要があることを伝えています。
リスト 7 ではまた、accelerate
メソッドと decelerate
メソッドをオーバーライドして、オートマチック車では加速と減速によっても、ギアが自動的に切り替えられるようにしています。この両方のメソッドでは、最初に親プロトタイプのメソッドを呼び出すため、実際の加速を再実装する必要はありません。したがって、コードの反復は少なくなります。この特定の例のコードはたった 1 行なので、反復コードを減らすことはそれほど重要ではありませんが、これが複雑な関数だとしたら、コードの繰り返しは大変な作業になっていたことでしょう。
最後に、この例を実際に使用する場合を見てください (リスト 8 を参照)。
リスト 8. オートマチック車プロトタイプを使用する
var myCar = new ATCar('10C500'); myCar.accelerate(30); myCar.accelerate(20); myCar.decelerate(5); console.log(myCar); |
このコードにより、上記で作成した新規オブジェクトが出力されます。オブジェクトをクリックして、その詳細を表示すると、図 1 のような結果となっているはずです。
図 1.
myCar
オブジェクトのプロパティーが表示された Firebug ビューこの記事の第 1 の目的は、Dojo のクラス・ベース OOP のシミュレーション機能の使い方を説明することなので、ここではごく普通の JavaScript でのプロトタイプ・ベースのオブジェクト指向の手法を紹介しているにすぎません。プロトタイプ・ベースの OOP についての詳細は、「参考文献」を参照してください。
クラス・ベースの OOP よりも、プロトタイプ・ベースの OOP のほうが優れているか、劣っているかについては、人によってさまざまな見方があります。賛成意見にしても、反対意見にしても、プログラマーとしての好みに基づいて反論するのはごく簡単です。プロトタイプ・ベースのオブジェクト指向で最もよくある問題の 1 つは、特に JavaScript をはじめ、多くの開発者たちの基本的な理解が足りないことです。JavaScript を深く理解する開発者の数が着実に増えているなか、この問題は徐々に解消されていてはいますが、それでもまだ、Java 言語のようなクラス・ベースの OOP 言語を使い慣れている多くのプログラマーはクラス・ベースのシステムに固執しています。そこで Dojo が提供しているのが、クラス・ベースのシステムをシミュレートした優れた機能の数々です。これらの機能を利用することで、クラス・ベースの OOP 言語と同じような方法で JavaScript のコーディングをすることができます。次のセクションでは、Dojo のこれらの機能を使用して、クラス・ベースのアプリケーションを JavaScript で作成する方法を説明します。
Dojo によるクラス・ベースの OOP のシミュレーション
Dojo のクラス・ベースのシミュレーションについて説明する前に、重要な点として言っておきますが、結局のところ、Dojo も JavaScript ライブラリーの 1 つです。Java コードと JavaScript は同じではありません。実際、この 2 つはかなり違います。Dojo は JavaScript を Java コードのように振る舞わせようとするのではなく、基礎となる構造がプロトタイプ・ベースの OOP でそのまま機能するようにしながら、Java (そして、その他のクラス・ベースの OOP 言語) の開発者が慣れ親しんだ方法で JavaScript OOP を扱えるようにします。
Dojo を使ってクラスを作成する場合には、dojo.declare
関数を使用します。では早速、この関数を使って Car
クラスを作成してみましょう (リスト 9 を参照)。
リスト 9.
dojo.declare
を使って作成する Car クラスdojo.declare("Car", null, { }); var myCar = new Car(); console.log(myCar); |
これは、クラスを作成し、そのクラスのオブジェクトをインスタンス化するための基本的なシェルです。dojo.declare
関数は以下の 3 つの引数を取ります。
- クラス名
- クラスが継承するスーパークラス
- クラスのプロパティーおよびメソッドからなるオブジェクト
リスト 9 の例で宣言している Carという名前のクラスは、スーパークラスも継承していなければ、メンバー変数やメソッドも規定していません。しかし、Firebug コンソールで出力をクリックすると、myCar
オブジェクトのオブジェクト・プロパティーは図 2 のように表示されます
図 2.
Dojo
クラスから作成された基本オブジェクトこの図に示されているように、Dojo でクラスを作成すると、そのクラスから生成されたオブジェクトにはデフォルトでプロパティーとメソッドが与えられます。現時点でのクラスはそれほど興味深いものではないので、プロパティーとメソッド、そしてコンストラクターを追加します。その方法は、前のセクションで説明したプロトタイプ・ベースの OOP での追加方法と同様です (リスト 10 を参照)。
リスト 10. より完全な
Car
クラスdojo.declare("Car", null, { reg_no: "", current_speed: 0, current_gear: 0, constructor: function(reg_no) { this.reg_no = reg_no; }, accelerate: function(increment) { this.current_speed += increment; }, decelerate: function(decrement) { this.current_speed -= decrement; }, increaseGear: function() { this.current_gear++; }, decreaseGear: function() { this.current_gear--; } }); |
ご覧のとおり、このスタイルのクラス宣言は遥かに読みやすく、一般的な JavaScript でオブジェクト・プロトタイプを宣言する方法よりも、まとまりがあります。継承についての話題に移る前に、オブジェクトをインスタンス化できること、そしてメソッドが機能することを確認します (リスト 11 を参照)。
リスト 11.
Car
クラスを使用するvar myCar = new Car("10C500"); myCar.accelerate(30); myCar.accelerate(20); myCar.decelerate(5); console.log(myCar.reg_no+" travelling at "+myCar.current_speed+" mph"); |
上記のコードによって、Firebug コンソールに 10C500 travelling at 45 mph
と出力されます。
次は、オートマチック車用のサブクラスを作成します。このサブクラスは、前に一般的な JavaScript を使って作成したサブクラスと似ていますが、今回は dojo.declare
関数を使って作成します (リスト 12 を参照)。
リスト 12. Dojo を使って作成する
ATCar
サブクラスdojo.declare("ATCar", Car, { accelerate: function(increment) { this.inherited(arguments); if(increment >= 10) this.increaseGear(); }, decelerate: function(decrement) { this.inherited(arguments); if(decrement >= 10) this.decreaseGear(); } }); |
リスト 12 からわかるように、サブクラスに指定するのは、新規またはオーバーライドされるプロパティーおよびメソッドだけです (この例では、自動ギア・チェンジを行うために、accelerate および decelerate メソッドをオーバーライドしているだけです)。スーパークラスでのコンストラクターの呼び出しは、Dojo が自動的に処理してくれます。コンストラクター関数を追加する必要がある場合には、サブクラスにコンストラクター関数を追加することができますが、スーパークラス・コンストラクターの呼び出しについて気を回す必要はありません。スーパークラス・コンストラクターは、自動的に呼び出されるからです。オーバーライドされる両方のメソッドで、行this.inherited(arguments)
が呼び出されていることにお気付きでしょうか。これは基本的に、スーパークラスの同じメソッドを呼び出しています。このように、実際に加速するためのコードを再作成する手間が省かれるため、オートマチック車で行われるギア・チャンジを容易に実現することができます。
ここで、この新しいサブクラスの動作を確認してみます (リスト 13 を参照)
リスト 13. 新規サブクラスの動作
var myCar = new ATCar("10C500"); myCar.accelerate(30); myCar.accelerate(20); myCar.decelerate(5); console.log(myCar.reg_no+" travelling at "+myCar.current_speed+" mph in gear "+myCar.current_gear); |
上記のコードによる出力は、10C500 travelling at 45 mph in gear 2
となります。
Dojo は多重継承もサポートします。多重継承では、1 つのサブクラスを複数の親クラスから派生させ、親クラスのそれぞれからプロパティーとメソッドを継承することができます。厳密に言うと、スーパークラスと見なされるのは親クラスのうちの 1 つ (継承元の親クラスを要素に持つ配列の先頭要素の親クラス) だけですが、それぞれの親クラスから派生したコンストラクターが呼び出される順番は、この配列に格納されている親クラスの順になります。
多重継承を具体的に説明するために Smartphone を例に取ると、Smartphone では音声通話の発着信とテキスト・メッセージの送受信の他にもさまざまな機能を実行します (リスト 14 を参照)。通常は音楽や動画の再生をはじめとするさまざまな機能を実行しますが、この例の目的は多重継承を説明することなので、単純に Phone では通話が可能、MediaPlayer では動画の再生が可能、Smartphone ではその両方が可能ということにします。
リスト 14. Dojo での多重継承
dojo.declare("Phone", null, { phone_number: "", minutes_remaining: 0, constructor: function(properties) { this.phone_number = properties.phone_number; this.minutes_remaining = properties.minutes_remaining; console.log("Phone "+this.phone_number+" powered on. You have "+this.minutes_remaining+" minute(s) remaining."); } }); dojo.declare("MediaPlayer", null, { disk_space: 0, constructor: function(properties) { this.disk_space = properties.disk_space; this.songs = properties.songs; console.log("Media Player powered on. You have "+this.songs.length+" songs, with "+this.disk_space+" GB free space left."); } }); dojo.declare("Smartphone", [Phone, MediaPlayer], { phone_id: "", constructor: function(properties) { this.phone_id = properties.phone_id; console.log("Smartphone ID "+this.phone_id+" boot up complete."); } }); var songs = [ {artist:"U2",title:"Vertigo"}, {artist:"Coldplay",title:"Yellow"} ]; var myPhone = new Smartphone({ phone_number:"(555) 123-4567", minutes_remaining: 60, disk_space: 2.5, songs: songs, phone_id: "4345FDFD7JAPO76" }); console.log(myPhone); |
最初に注目すべき点は、dojo.declare
が多重継承を実装する方法です。上記を見るとわかるように、この関数には単純に親クラスが 2 番目の引数として渡されるのではなく、クラスの配列が渡されます。これらの親クラスのコンストラクターは、配列に並べられている順に自動的に呼び出されます。ここで注意しなければならない重要な点は、それぞれの親クラスのコンストラクターが取る引数はそれぞれに異なることです。Dojo は、各コンストラクター関数に渡さなければならない引数を区別することができません。そのため、異なるコンストラクターに異なる引数を渡さなければならない場合には、引数を単純な JavaScript でキーと値のペアとして追加し、コンストラクターでも引数を鍵と値のペアとして使用する必要があります。
リスト 15 に、Firebug コンソールに表示される出力を記載します。
リスト 15. 多重継承の例による出力
Phone (555) 123-4567 powered on. You have 60 minute(s) remaining. Media Player powered on. You have 2 songs, with 2.5 GB free space left. Smartphone ID 4345FDFD7JAPO76 boot up complete. Object { phone_number="(555) 123-4567", more...} |
最後の行のリンクをクリックすると、図 3 のような myPhone
オブジェクトのプロパティーが表示されます。
図 3. 複数の親クラスを継承した
myPhone
オブジェクト図 3 では、異なるクラスの異なるプロパティーのすべてが、Smartphone
クラスのインスタンス化されたオブジェクト内に存在しています。具体的には Phone
クラスの phone_number
プロパティーとmintues_remaining
プロパティー、MediaPlayer
クラスの disk_space
プロパティーと songs
プロパティー、そして Smartphone
サブクラスの phone_id
メンバー変数です。これらのクラスがメソッドを持っているとすれば、それらのメソッドもここに表示されることになります。
Dojo には、dojo.mixin
という便利なユーティリティー関数が用意されています。この関数を使用すると、オブジェクトのプロパティーを左から右の順に組み合わせて、複数のオブジェクトを 1 つに結合することができます (リスト 16 を参照)。
リスト 16. 基本的な dojo.mixin の例
var objA = { a: 1, b: 2 }; var objB = { b: 3, c: 4 }; dojo.mixin(objA, objB); console.log(objA); |
objB
を objA
に結合すると、objA
のプロパティーは図 4 に示すようになります。
図 4.
dojo.mixin
によるオブジェクトの結合b
プロパティーは、objA
では当初 2
に設定されていましたが、objB
での値 3
で上書きされています。また、結合されたオブジェクトには、c
プロパティーも追加されています。基本的な例を抑えたところで、前のセクションで取り上げた多重継承の例で dojo.mixin
を利用する方法を検討します。
前の例で Phone
クラスを作成したときに、リスト 17 に記載する 2 つの行をクラスのコンストラクターに含めたことを思い出してください。
リスト 17.
Phone
クラス・コンストラクター内の行this.phone_number = properties.phone_number; this.minutes_remaining = properties.minutes_remaining; |
このクラスにはプロパティーが 2 つしかなかったので、それほど面倒な作業ではありませんでしたが、多数のプロパティーがある場合を考えてみてください。このようにプロパティーを割り当てるのでは、かなりの苦痛になると思いませんか?dojo.mixin
関数の便利さを実感するのは、まさにこのような場合です。上記の 2 つの行を (さらに、MediaPlayer
クラスと Smartphone
クラスの同様の行も)、dojo.mixin(this, properties);
に置き換えてください。
結果は前とまったく変わりませんが、コンストラクターに渡していた各種のプロパティーに煩わされることはもうありません。
大規模なアプリケーションに取り組んでいると、メンバー変数とメソッドが数多く設定された大きなクラスを扱うことになりがちです。Java 開発のバックグラウンドを持つ開発者であれば、異なるクラスをそれぞれ別個のファイルに置き、パッケージによってグループ化するという考え方に従うことでしょう。そして、継承やその他の目的で必要になったときにクラスを「インポート」して、必要なときにだけクラスがロードされるようにするはずです。JavaScript にはすぐに使用できるパッケージおよびモジュール・システムが用意されていませんが、ありがたいことに、Dojo がソリューションを提供してくれます。
Car
クラスを例にとると、このクラスをパッケージに保管するには、リスト 18 の Java コードを使用します。
リスト 18. Java プログラミングでのクラスのパッケージ化
package com.ibm.developerworks.dojoseries; public class Car { //Car class code goes here } |
これで、このクラスを他の Java クラスにインポートできるようになります (リスト 19 を参照)。
リスト 19. Java プログラミングでのクラスのインポート
package com.ibm.developerworks.dojoseries; import com.ibm.developerworks.dojoseries.Car; public class ATCar extends Car { //ATCar class code goes here } |
Dojo は同様のパッケージ・システムを、dojo.provide
および dojo.require
関数という手段で提供します。リスト 19 に記載した Java コードが Dojo ではどのようになるかを調べてみましょう。まず、リスト 20 の Car
クラスを見てください。
リスト 20. Dojo でのクラスのパッケージ化
dojo.provide("com.ibm.developerworks.dojoseries.Car"); dojo.declare("com.ibm.developerworks.dojoseries.Car", null, { //Car class code goes here }); |
お気付きのように、上記のコードは Java コードとかなりよく似ていますが、dojo.provide
文にはクラスを含めるパッケージのパスだけでなく、クラスの完全なパッケージ・パスが指定されています。パッケージ・パスは重要です。なぜなら、Dojo が dojo.require
を使用してクラスをロードしようとするときに、クラスを検索する場所を決定するのもパッケージ・パスだからです。従って、リスト 20 の例では Car.js ファイルは相対パス com/ibm/developerworks/dojoseries/Car.js に保管されるはずです。もしこの場所に保管されない場合には、Dojo は必要なときに Car クラスをロードすることができなくなってしまいます。次に、このクラスをインポートして、そこからサブクラスを作成する方法を見てみましょう (リスト 21 を参照)。
リスト 21. Dojo でのクラスのインポート
dojo.provide("com.ibm.developerworks.dojoseries.ATCar"); dojo.require("com.ibm.developerworks.dojoseries.Car"); dojo.declare("com.ibm.developerworks.dojoseries.ATCar", com.ibm.developerworks.dojoseries.Car, { //ATCar class code goes here }); |
上記で注目する点は、新しいサブクラスをどこから (どのパスから) ロードできるのかを指定するために、ここでも dojo.provide 文が使用されていることです。この例では、この特定のクラスを相対パス com/ibm/develoeprworks/dojoseries/ATCar.js に保管するように指定しています。続いて dojo.require
を使用し、クラスの完全なパッケージ・パスを使って Car
クラスをロードします。そして最後に、親クラスの完全なパスを 2 番目の引数として渡して、サブクラスを宣言しています。これで Dojo によってクラスがロードされたので、そのクラスを DOM で使用可能になり、クラスの名前をストリングに配置しなくても、その名前を指定することで直接クラスをロードすることができます。
理論的には、dojo.provide
文に指定したパッケージ・パスとは異なるクラス名を宣言することもできます (ただし、dojo.require
を使用してクラスをロードするときには、必ず、dojo.provide
に指定されている完全修飾パスを使用する必要があります)。けれども、異なる名前を使用すると混乱の元になるだけなので、この方法はお勧めしません。
次のセクションでは、アプリケーションが最適なパフォーマンスと速度で実行されるように、Dojo のビルド・システムを使用してアプリケーションをパッケージ化する方法を説明します。
Dojo ビルド・システムを使用して、すべてを 1 つにパッケージ化する方法
Dojo のオブジェクト指向の機能を扱う際には、コードの編成と管理をごく簡単にするために、まずはクラスをそれぞれのファイルに区別するところから取り掛かるはずです。けれども、ここで注意しなければならない重要な点は、多数の小さな JavaScript ファイルをロードするとなると、アプリケーションのパフォーマンスに深刻な影響を与える可能性があることです。Web ブラウザーが JavaScript ファイルをダウンロードして実行しなければならないときには、その度に HTTP リクエストを行い、サーバーからのレスポンスを待機し、返ってきたレスポンスを処理することになります。したがって、多数の小さなファイルをロードするよりは、1 つの大きなファイルをロードしたほうが時間を大幅に短縮できるのが通常です。
1 つの大きなファイルを使用する場合の問題は、実際にはアプリケーションのどの部分にも必要でない大量のコードがファイルに含まれる可能性があることは言うまでもなく、バージョン管理およびコードの編成が、悪夢のような作業になりかねないことです。この問題に対するソリューションは、ファイル・サイズを小さく保つことと、アプリケーションが行う HTTP リクエストの数を最小限にすることとの間で適切なバランスを保つことです。
そこで威力を発揮するのが、Dojo のビルド・システムです。Dojo のビルド・システムでは複数のレイヤーを定義し、それぞれのレイヤーに複数の JavaScript ソース・ファイルからのソース・コードをまとめ、最終的に 1 つにまとめたファイルを縮小化して、ファイルのサイズを最小限に抑えることができます。この縮小化プロセスは、不要なホワイト・スペースとコメントをすべて取り除き、さらにローカル変数を短縮名に変更して、関数内でのローカル変数の使い方を必要に応じてリファクタリングすることによって、コードを大幅に縮小します。Dojo ビルド・システムを使用することで、開発中にソース・コードを整理しておくことができますが、本番環境にデプロイする際には、必ずコードが最適なパフォーマンス・レベルで機能するようにしてください。
Dojo ビルド・システムの使用方法を解説するには、それだけで 1 つの記事が必要になるほどです。幸い、このトピックについては Dojo のドキュメントで広範にわたって説明しています。ビルド・システムの使用方法についての詳細は、「参考文献」を参照してください。
Dojo Toolkit を使用してリッチな Web ベースのアプリケーションを開発する方法について説明する 3 回連載の第 2 回では、オブジェクト指向の基本、JavaScript で使用しているプロトタイプ・ベースの OOP 手法、そして昔からの開発者の多くが長いこと抱いている、JavaScript が強力な OOP 対応言語ではないという認識がどのような点で誤りであるかを説明しました。続いて、JavaScript の一般的なオブジェクト指向の機能について説明し、オブジェクト・プロトタイプの定義方法、継承の実装方法を具体的な例で示しました。また、コードをファイルに保存することなく、Firebug を使用して JavaScript コードをテストする方法もわかったはずです。さらに、Dojo の dojo.declare
関数を使用して、Java 開発者が慣れ親しんでいる形式で JavaScript クラスを作成する方法、継承と多重継承を実行する方法、そして dojo.mixin
によってごく簡単にオブジェクトのプロパティーを結合する方法を学びました。さらに、Dojoのパッケージおよびモジュール関数を使用すれば、Java や C++ のような従来の OO 言語で行うような方法で、クラスを個々のソース・ファイルに分けられることを説明し、最後に Dojo ビルド・システムでは、本番環境でのファイル・サイズと HTTP リクエストを最小限に抑えることによって、パフォーマンスに影響を及ぼすことなく、容易に簡潔なコード編成ができるような方法で開発できることも説明しました。
連載最終回となる第 3 回では、Dojo のウィジェット・プラットフォームである Dijit を使用して、インタラクティブなリッチ・インターネット・アプリケーションを作成する方法を紹介します。Dijit は Dojo のオブジェクト指向機能をベースに作成されているので、次回の記事では今回学んだ内容が役立つことが証明されるはずです。
0 件のコメント:
コメントを投稿