| ActionScript 3.0 のプログラミング > ActionScript プログラミングの概要 > ActionScript のオブジェクト指向プログラミング > 高度なテクニック | |||
このセクションでは、最初に ActionScript と OOP の歴史について簡単に説明します。次に、ActionScript 3.0 オブジェクトモデル、およびそれによって新しい ActionScript 仮想マシン (AVM2) を古い ActionScript 仮想マシン (AVM1) を使用する旧バージョンの Flash Player に対して大幅に高速に実行可能にする方法について解説します。
ActionScript 3.0 は旧バージョンの ActionScript をベースに構築されているため、ActionScript オブジェクトモデルがどのように進化してきたかを理解しておくと役立ちます。ActionScript は、Flash オーサリングツールの初期バージョン用の単純なスクリプトメカニズムとして生まれました。その後、プログラマは ActionScript を使用してより複雑なアプリケーションを作成し始めました。プログラマのニーズに応えるために、その後の各リリースには複雑なアプリケーションの作成を容易にする言語機能が追加されてきました。
ActionScript 1.0 は、Flash Player 6 以前で使用されていた言語のバージョンです。この開発初期段階でも、ActionScript オブジェクトモデルは基本的なデータ型としてのオブジェクトの概念をベースにしていました。ActionScript オブジェクトは、"プロパティ" のグループを持つ複合データ型です。オブジェクトモデルについて説明するとき、"プロパティ"という用語には、変数、関数、メソッドなど、オブジェクトに関連付けられるあらゆるものが含まれます。
第 1 世代の ActionScript では class キーワードによるクラスの定義はサポートされませんが、プロトタイプオブジェクトと呼ばれる特殊な種類のオブジェクトを使用してクラスを定義することができます。class キーワードを使用して具象オブジェクトにインスタンス化する抽象クラスの定義を作成するのではなく、Java や C++ などのクラスベース言語の場合と同様に、ActionScript 1.0 のようなプロトタイプベース言語では、既存オブジェクトを他のオブジェクトのモデル (またはプロトタイプ) として使用します。クラスベース言語のオブジェクトはテンプレートになるクラスを示す場合がありますが、プロトタイプベース言語のオブジェクトはテンプレートになる別のオブジェクト、つまりプロトタイプを示します。
ActionScript 1.0 でクラスを作成するには、クラスのコンストラクタ関数を定義します。ActionScript の関数は、単に抽象的な定義ではなく実際のオブジェクトです。作成したコンストラクタ関数は、そのクラスのインスタンスのプロトタイプ的なオブジェクトになります。次のコードは、Shape というクラスを作成し、デフォルトで true に設定される visible というプロパティを 1 つ定義します。
// 基本クラス
function Shape() {}
// visible というプロパティを作成する。
Shape.prototype.visible = true;
このコンストラクタ関数は、次のように new 演算子でインスタンス化できる Shape クラスを定義します。
myShape = new Shape();
Shape コンストラクタ関数オブジェクトが Shape クラスのインスタンスのプロトタイプになるように、これは Shape のサブクラスのプロトタイプ、つまり Shape クラスを拡張するその他のクラスにもなります。
Shape クラスのサブクラスであるクラスを作成するには、次の 2 つの手順を実行します。最初に、次のようにクラスのコンストラクタ関数を定義して、クラスを作成します。
// 子クラス
function Circle(id, radius)
{
this.id = id;
this.radius = radius;
}
次に、new 演算子を使用して、Shape クラスが Circle クラスのプロトタイプであると宣言します。
デフォルトでは、作成したクラスはそのプロトタイプとして Object クラスを使用します。つまり、Circle.prototype には現在汎用オブジェクト (Object クラスのインスタンス) が含まれています。Circle のプロトタイプに Object ではなく Shape を指定するには、汎用オブジェクトではなく Shape オブジェクトが含まれるように、次のコードを使用して Circle.prototype の値を変更します。
// Circle を Shape のサブクラスにする Circle.prototype = new Shape();
これで、Shape クラスと Circle クラスは "プロトタイプチェーン" と呼ばれる継承関係内でリンクされました。次の図は、プロトタイプチェーン内の関係を示します。
各プロトタイプチェーンの最後にある基本クラスは Object クラスです。Object クラスには、ActionScript 1.0 で作成されたすべてのオブジェクトの基本プロトタイプオブジェクトを指す Object.prototype という静的プロパティが含まれます。このプロトタイプチェーンの次のオブジェクトは Shape オブジェクトです。これは、Shape.prototype プロパティは明示的に設定されていないため、依然として汎用オブジェクト (Object クラスのインスタンス) を保持しているからです。プロトタイプチェーンの最後のリンクは Circle クラスで、プロトタイプの Shape クラスにリンクされています。Circle.prototype プロパティは Shape オブジェクトを保持します。
次の例に示すように、Circle クラスのインスタンスを作成すると、インスタンスは Circle クラスのプロトタイプチェーンを継承します。
// Circle クラスのインスタンスを作成する myCircle = new Circle();
Shape クラスのメンバーとして visible というプロパティを既に作成しました。例では、visible プロパティは、myCircle オブジェクトの一部としてではなく Shape オブジェクトのメンバーとしてのみ存在しますが、次のコード行では true が出力されます。
trace(myCircle.visible); // true
Flash Player では、プロトタイプチェーン内を移動することにより、myCircle オブジェクトが visible プロパティを継承することを確認できます。このコードを実行すると、Flash Player は最初に myCircle オブジェクトのプロパティから visible というプロパティを検索しますが、このプロパティは見つかりません。Flash Player は次に Circle.prototype オブジェクトを検索しますが、visible というプロパティは見つかりません。Flash Player はプロトタイプチェーン内を引き続き検索し、最後に Shape.prototype オブジェクトで定義された visible プロパティを見つけ、そのプロパティの値を出力します。
簡潔にするために、このセクションではプロトタイプチェーンの詳細および複雑さに関する説明の多くを省略し、代わりに ActionScript 3.0 オブジェクトモデルの理解に役立つ情報を提供します。
ActionScript 2.0 では、Java や C++ などのクラスベース言語の経験があればなじみのある方法でクラスを定義できる class、extends、public、および private などの新しいキーワードが導入されました。ActionScript 1.0 と ActionScript 2.0 では基になる継承メカニズムが変更されていないことを理解しておくことが重要です。ActionScript 2.0 では、クラスを定義するための新しいシンタックスが追加されただけです。プロトタイプチェーンは、この 2 つのバージョンで同じように機能します。
次の抜粋に示すように ActionScript 2.0 で導入された新しいシンタックスでは、より直観的な方法でクラスを定義できます。
// 基本クラス
class Shape
{
var visible:Boolean = true;
}
ActionScript 2.0 では、コンパイル時の型チェックで使用するための型注釈も導入されました。これにより、前述の例の visible プロパティにはブール値だけが含まれることを宣言できます。また、新しい extends キーワードにより、サブクラスを作成するプロセスが簡略化されます。次の例では、ActionScript 1.0 では 2 つの手順が必要なプロセスが、extends キーワードにより 1 つの手順で実行されています。
// 子クラス
class Circle extends Shape
{
var id:Number;
var radius:Number;
function Circle(id, radius)
{
this.id = id;
this.radius = radius;
}
}
コンストラクタは、クラス定義の一部として宣言されています。クラスプロパティ id と radius も明示的に宣言する必要があります。
ActionScript 2.0 ではインターフェイス定義のサポートも追加され、オブジェクト間の通信用に正式に定義されたプロトコルでオブジェクト指向プログラムをさらに改良できるようになりました。
通常 Java や C++ に関連付けられる一般的なオブジェクト指向プログラミングパラダイムでは、クラスを使用してオブジェクトの型を定義します。このパラダイムを採り入れたプログラミング言語も、クラスにより定義されるデータ型のインスタンスを作成するためにクラスを使用する傾向にあります。ActionScript では、この両方の目的でクラスを使用しますが、プロトタイプベース言語であることから興味深い特徴が付け加えられています。ActionScript ではクラス定義ごとに、動作と状態の両方を共有できる特別なクラスオブジェクトを作成します。とは言え、コーディングする上でこの違いが実質的な影響があると感じる ActionScript プログラマはほとんどいないでしょう。ActionScript 3.0 は、この特別なオブジェクトを使用しなくても、さらには理解していなくても、高度なオブジェクト指向 ActionScript アプリケーションを作成できるように設計されています。このセクションでは、クラスオブジェクトを最大限に活用したい熟練プログラマを対象に、この問題を掘り下げて説明します。
次の図は、class A {} ステートメントで定義された A という名前の単純なクラスを表すクラスオブジェクトの構造を示します。
図中の四角形はオブジェクトを表します。図中のオブジェクトには、クラス A に属していることを表す下付き文字 A が付いています。クラスオブジェクト (CA) には、その他の重要なオブジェクトへの参照が含まれます。インスタンス特性オブジェクト (TA) には、クラス定義内で定義されたインスタンスプロパティが格納されます。クラス特性オブジェクト (TCA) は、クラスの内部型を表し、そのクラスにより定義された静的プロパティを格納します。下付き文字 C は "クラス" を表します。プロトタイプオブジェクト (PA) は、constructor プロパティで最初に関連付けられたクラスオブジェクトを常に参照します。
ActionScript 3.0 で新しく導入された特性オブジェクトは、パフォーマンスを考慮して実装されました。旧バージョンの ActionScript では、名前のルックアップは、Flash Player がプロトタイプチェーン内を移動するため時間のかかるプロセスでした。ActionScript 3.0 では、継承されたプロパティがスーパークラスからサブクラスの特性オブジェクトにコピーされるので、名前のルックアップは効率的になり時間が短縮されました。
特性オブジェクトはプログラマコードに直接アクセスできませんが、パフォーマンスが向上しメモリ使用量が削減することからオブジェクトの存在がわかります。特性オブジェクトは、AVM2 にクラスのレイアウトと内容に関する詳細情報を提供します。この情報があれば、AVM2 は直接マシン命令を生成して時間のかかる名前のルックアップを行わずにプロパティにアクセスしたり、メソッドを直接呼び出したりすることができるため、実行時間を大幅に短縮できます。
特性オブジェクトのおかげで、オブジェクトのメモリフットプリントは、旧バージョンの ActionScript の同様のオブジェクトと比較してかなり小さくすることができます。たとえば、クラスが sealed の場合 (つまり、クラスが dynamic と宣言されていない場合)、クラスのインスタンスは動的に追加するプロパティにハッシュテーブルを必要とせず、このクラスで定義された固定プロパティの特性オブジェクトおよびスロットへのポインタを保持するだけです。その結果、ActionScript 2.0 で 100 バイトのメモリが必要だったオブジェクトが、ActionScript 3.0 では 20 バイトで済みます。
|
メモ |
|
特性オブジェクトは、内部実装の詳細です。ActionScript の今後のバージョンで変更されない、またはなくならないとは限りません。 |
ActionScript のクラスオブジェクトには、クラスのプロトタイプオブジェクトを参照する prototype プロパティがあります。プロトタイプオブジェクトは、ActionScript のプロトタイプベース言語としてのルーツのレガシーです。詳細については、ActionScript 1.0を参照してください。
prototype プロパティは読み取り専用で、別のオブジェクトを指すように変更することはできません。これは、旧バージョンの ActionScript のクラスの prototype プロパティとは異なります。旧バージョンのプロパティでは、別のクラスを指すようにプロトタイプを再割り当てできました。prototype プロパティは読み取り専用ですが、参照されるプロトタイプオブジェクトは読み取り専用ではありません。つまり、プロトタイプオブジェクトに新しいプロパティを追加することができます。プロトタイプオブジェクトに追加されたプロパティは、クラスのすべてのインスタンス間で共有されます。
旧バージョンの ActionScript では唯一の継承メカニズムであったプロトタイプチェーンは、ActionScript 3.0 では二次的な役割のみを果たします。主要継承メカニズムである固定プロパティの継承は、特性オブジェクトによって内部的に処理されます。固定プロパティは、クラス定義の一部として定義される変数またはメソッドです。固定プロパティの継承は、class、extends、override などのキーワードで関連付けられる継承メカニズムであるため、クラス継承とも呼ばれます。
プロトタイプチェーンは、固定プロパティの継承より動的な代替継承メカニズムになります。プロパティは、クラス定義の一部としてだけでなく、実行時にクラスオブジェクトの prototype プロパティからもクラスのプロトタイプオブジェクトに追加することができます。ただし、コンパイラを strict モードに設定した場合は、クラスを dynamic キーワードで宣言しない限りプロトタイプオブジェクトに追加されたプロパティにアクセスできない場合があります。
いくつものプロパティがプロトタイプオブジェクトに関連付けられているクラスの好例として、Object クラスがあります。Object クラスの toString() と valueOf() メソッドは、実際には Object クラスのプロトタイプオブジェクトのプロパティに割り当てられた関数です。次の例は、これらのメソッドの宣言が理論的にどのように見えるかを示します。ただし、実装の詳細により実際の実装はやや異なります。
public dynamic class Object
{
prototype.toString = function()
{
// ステートメント
};
prototype.valueOf = function()
{
// ステートメント
};
}
前述したように、プロパティは、クラス定義外部でクラスのプロトタイプオブジェクトに関連付けることができます。たとえば、toString() メソッドは、次のように Object クラスの定義外部で定義することもできます。
Object.prototype.toString = function()
{
// ステートメント
};
しかし、固定プロパティの継承とは異なり、サブクラスでメソッドを再定義する場合、プロトタイプの継承では override キーワードが必要ありません。たとえば、Object クラスのサブクラスで valueOf() メソッドを再定義する場合、次の 3 つのオプションがあります。1 つ目は、クラス定義内部のサブクラスのプロトタイプオブジェクトで valueOf() メソッドを定義します。次のコードは、Foo という Object のサブクラスを作成し、Foo のプロトタイプオブジェクトでクラス定義の一部として valueOf() メソッドを再定義します。どのクラスも Object を継承するため、extends キーワードを使用する必要はありません。
dynamic class Foo
{
prototype.valueOf = function()
{
return "Instance of Foo";
};
}
2 つ目は、次のコードに示すように、クラス定義外にある Foo のプロトタイプオブジェクトで valueOf() メソッドを定義します。
Foo.prototype.valueOf = function()
{
return "Instance of Foo";
};
3 つ目は、Foo クラスの一部として valueOf() という固定プロパティを定義します。この方法は、固定プロパティの継承とプロトタイプの継承が混在するという点で他の 2 つとは異なります。valueOf() を再定義する Foo のサブクラスでは、override キーワードを使用する必要があります。次のコードは、Foo で固定プロパティとして定義された valueOf() を示します。
class Foo
{
function valueOf() {
return "Instance of Foo";
}
}
固定プロパティの継承とプロトタイプの継承という 2 つの別々の継承メカニズムが存在すると、コアクラスのプロパティおよびメソッドに関して、興味深い互換性の問題が生じます。ECMAScript Edition 4 言語仕様案との互換性からは、プロトタイプの継承を使用する必要があります。つまり、コアクラスのプロパティおよびメソッドは、そのクラスのプロトタイプオブジェクトで定義されます。一方、Flash Player API との互換性では、固定プロパティの継承を使用することが要求されます。つまり、コアクラスのプロパティおよびメソッドは、const、var、および function のキーワードを使用してクラス定義で定義されます。さらに、プロトタイプバージョンの代わりに固定プロパティを使用することで、ランタイムパフォーマンスを著しく向上させることが可能です。
ActionScript 3.0 では、この問題を解決するために、コアクラスにプロトタイプの継承と固定プロパティの継承の両方を使用しています。各コアクラスに、2 セットのプロパティおよびメソッドが含まれます。1 セットは、ECMAScript 仕様との互換性のためにプロトタイプオブジェクトで定義され、残りの 1 セットは、Flash Player API との互換性のために固定プロパティおよび AS3 名前空間で定義されます。
AS3 名前空間は、この 2 セットのプロパティおよびメソッド間の選択のために便利なメカニズムを提供します。AS3 名前空間を使用しない場合、コアクラスのインスタンスは、コアクラスのプロトタイプオブジェクトに定義されているプロパティおよびメソッドを継承します。AS3 名前空間を使用することに決めた場合は、固定プロパティがプロトタイププロパティよりも常に優先されるため、コアクラスのインスタンスは AS3 バージョンを継承します。つまり、固定プロパティが利用可能な場合には、同じ名前のプロトタイププロパティではなく、その固定プロパティが必ず使用されます。
AS3 名前空間で修飾することによって、AS3 名前空間バージョンのプロパティまたはメソッドを選択的に使用することができます。たとえば、次のコードでは、AS3 バージョンの Array.pop() メソッドを使用しています。
var nums:Array = new Array(1, 2, 3); nums.AS3::pop(); trace(nums); // output: 1,2
代わりに、use namespace ディレクティブを使用してコードブロック内のすべての定義に対して AS3 名前空間を開くことができます。たとえば、次のコードでは、use namespace ディレクティブを使用して pop() メソッドと push() メソッドの両方に対して AS3 名前空間を開いています。
use namespace AS3; var nums:Array = new Array(1, 2, 3); nums.pop(); nums.push(5); trace(nums) // output: 1,2,5
ActionScript 3.0 では、AS3 名前空間をプログラム全体に適用できるように、プロパティセットごとにコンパイラオプションも用意されています。-as3 コンパイラオプションは AS3 名前空間を表し、-es コンパイラオプション (es は ECMAScript の略) はプロトタイプ継承オプションを表します。プログラム全体に対して AS3 名前空間を開くには、-as3 コンパイラオプションを true に、-es コンパイラオプションを false に設定します。プロトタイプバージョンを使用するには、両方のコンパイラオプションを反対の値に設定します。Adobe Flex Builder 2 および Adobe Flash CS3 Professional のデフォルトのコンパイラ設定は、-as3 = true および -es = false です。
いずれかのコアクラスを拡張してメソッドのいずれかをオーバーライドする計画がある場合には、オーバーライドメソッドの宣言方法に AS3 名前空間がどのように影響する可能性があるかを理解しておく必要があります。AS3 名前空間を使用する場合は、コアクラスメソッドのいずれのメソッドオーバーライドも、override 属性で AS3 名前空間を使用する必要があります。AS3 名前空間を使用せずにコアクラスメソッドをサブクラスで再定義する場合は、AS3 名前空間も override キーワードも使用しないでください。
Flex 2.01