高度なトピック

ActionScript OOP サポートの歴史

ActionScript 3.0 は旧バージョンの ActionScript をベースに構築されているため、ActionScript オブジェクトモデルがどのように進化してきたかを理解しておくと役立ちます。 ActionScript は、Flash Professional の初期バージョン用の単純なスクリプトメカニズムとして誕生しました。その後、プログラマーは ActionScript を使用してより複雑なアプリケーションを作成するようになりました。プログラマーのニーズに応えるために、その後の各リリースには複雑なアプリケーションの作成を容易にする言語機能が追加されてきました。

ActionScript 1.0

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 つ定義します。

// base class 
function Shape() {} 
// Create a property named visible. 
Shape.prototype.visible = true;

このコンストラクター関数は、new 演算子でインスタンス化できる Shape クラスを次のように定義します。

myShape = new Shape();

Shape() コンストラクター関数オブジェクトが Shape クラスのインスタンスのプロトタイプとして機能するのと同様に、これは Shape のサブクラスのプロトタイプ、すなわち Shape クラスを拡張する他のクラスとしても機能します。

Shape クラスのサブクラスであるクラスを作成するには、次の 2 つの手順を実行します。 最初に、次のようにクラスのコンストラクター関数を定義して、クラスを作成します。

// child class 
function Circle(id, radius) 
{ 
this.id = id; 
this.radius = radius; 
}

次に、new 演算子を使用して、Shape クラスが Circle クラスのプロトタイプであると宣言します。デフォルトでは、作成したクラスはそのプロトタイプとして Object クラスを使用します。つまり、現時点では、Circle.prototype には汎用オブジェクト(Object クラスのインスタンス)が含まれています。Circle のプロトタイプに Object ではなく Shape を指定するには、汎用オブジェクトではなく Shape オブジェクトが含まれるように、次のコードを使用して Circle.prototype の値を変更します。

// Make Circle a subclass of 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 クラスのプロトタイプチェーンを継承します。

// Create an instance of the Circle class. 
myCircle = new Circle();

以前に、Shape クラスのメンバーとして、visible という名前のプロパティを作成しました。この例では、visible プロパティは、myCircle オブジェクトの一部としてではなく Shape オブジェクトのメンバーとしてのみ存在しますが、次のコード行では true が出力されます。

trace(myCircle.visible); // output: true

ランタイムでは、プロトタイプチェーン内を移動することにより、myCircle オブジェクトが visible プロパティを継承することを確認できます。このコードを実行すると、ランタイムは最初に myCircle オブジェクトのプロパティから visible というプロパティを検索しますが、このプロパティは見つかりません。次に Circle.prototype オブジェクトを検索しますが、やはり visible というプロパティは見つかりません。ランタイムは、プロトタイプチェーン内を引き続き検索し、最後に Shape.prototype オブジェクトで定義された visible プロパティを見つけ、そのプロパティの値を出力します。

簡潔にするために、ここでは、プロトタイプチェーンの詳細および複雑さに関する説明の多くを省略します。代わりに ActionScript 3.0 オブジェクトモデルの理解に役立つ情報を提供します。

ActionScript 2.0

ActionScript 2.0 では、Java や C++ などのクラスベース言語の経験がある方には使い慣れた方法でクラスを定義できる classextendspublicprivate などの新しいキーワードが導入されました。基礎となる継承メカニズムは ActionScript 1.0 と ActionScript 2.0 で変わりがないことを理解する必要があります。ActionScript 2.0 では、クラスの定義に使用する新しいシンタックスが追加されたにすぎません。プロトタイプチェーンは、両方の言語バージョンで同じように機能します。

次の抜粋に示すように ActionScript 2.0 で導入された新しいシンタックスでは、より直観的な方法でクラスを定義できます。

// base class 
class Shape 
{ 
var visible:Boolean = true; 
}

ActionScript 2.0 では、コンパイル時の型チェックに使用するための型注釈も導入されました。 これにより、前述の例の visible プロパティにはブール値だけが含まれることを宣言できます。また、新しい extends キーワードにより、サブクラスを作成するプロセスが簡略化されます。次の例では、ActionScript 1.0 では 2 つの手順が必要なプロセスが、extends キーワードにより 1 つの手順で実行されています。

// child class 
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 ではインターフェイス定義のサポートも追加され、オブジェクト間の通信用に正式に定義されたプロトコルでオブジェクト指向プログラムをさらに改良できるようになりました。

ActionScript 3.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 OOP サポートの歴史を参照してください。

prototype プロパティは読み取り専用で、別のオブジェクトを指すように変更することはできません。これは、旧バージョンの ActionScript のクラスの prototype プロパティとは異なります。旧バージョンのプロパティでは、別のクラスを指すようにプロトタイプを再割り当てできました。prototype プロパティは読み取り専用ですが、参照されるプロトタイプオブジェクトは読み取り専用ではありません。つまり、プロトタイプオブジェクトに新しいプロパティを追加することができます。 プロトタイプオブジェクトに追加されたプロパティは、クラスのすべてのインスタンス間で共有されます。

ActionScript の以前のバージョンでは唯一の継承メカニズムであったプロトタイプチェーンは、ActionScript 3.0 では二次的な役割を果たすだけです。主な継承メカニズムである固定プロパティの継承は、特性オブジェクトによって内部的に処理されます。固定プロパティは、クラス定義の一部として定義される変数またはメソッドです。 固定プロパティの継承は、classextendsoverride などのキーワードで関連付けられる継承メカニズムなので、クラス継承とも呼ばれます。

プロトタイプチェーンは、固定プロパティの継承より動的な代替継承メカニズムになります。 プロパティは、クラス定義の一部としてだけでなく、実行時にクラスオブジェクトの prototype プロパティからもクラスのプロトタイプオブジェクトに追加できます。ただし、コンパイラーを strict モードに設定した場合は、クラスを dynamic キーワードで宣言しない限り、プロトタイプオブジェクトに追加されたプロパティにアクセスできない場合があります。

いくつものプロパティがプロトタイプオブジェクトに関連付けられているクラスの好例として、Object クラスがあります。 Object クラスの toString() および valueOf() メソッドは、実際は Object クラスのプロトタイプオブジェクトのプロパティに割り当てられた関数です。次の例は、これらのメソッドの宣言が理論的にどのように見えるかを示します。ただし、実装の詳細により実際の実装はやや異なります。

public dynamic class Object 
{ 
    prototype.toString = function() 
    { 
        // statements 
    }; 
    prototype.valueOf = function() 
    { 
        // statements 
    }; 
}

前述のとおり、プロパティは、クラス定義外部でクラスのプロトタイプオブジェクトに関連付けることができます。例えば、toString() メソッドは、次のように Object クラスの定義外部で定義することもできます。

Object.prototype.toString = function() 
{ 
    // statements 
};

ただし、固定プロパティの継承とは異なり、プロトタイプの継承では、サブクラスでメソッドを再定義する場合、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():String 
    { 
        return "Instance of Foo"; 
    } 
}

AS3 名前空間

固定プロパティの継承およびプロトタイプの継承という 2 つの別個の継承メカニズムが混在することで、コアクラスのプロパティおよびメソッドに関して留意すべき互換性の問題が生じます。 ActionScript が基づく ECMAScript 言語仕様との互換性からは、プロトタイプの継承を使用する必要があります。つまり、コアクラスのプロパティおよびメソッドは、そのクラスのプロトタイプオブジェクトで定義されます。一方、ActionScript 3.0 との互換性では、固定プロパティの継承を使用することが要求されます。つまり、コアクラスのプロパティおよびメソッドは、constvar、および function のキーワードを使用してクラス定義で定義されます。さらに、プロトタイプではなく固定プロパティの継承を使用すると、ランタイムパフォーマンスの大幅な向上につながります。

ActionScript 3.0 では、この問題を解決するために、コアクラスにプロトタイプの継承と固定プロパティの継承の両方を使用しています。 各コアクラスに、2 セットのプロパティおよびメソッドが含まれます。 1 セットは、ECMAScript 仕様との互換性のためにプロトタイプオブジェクトで定義され、残りの 1 セットは、ActionScript 3.0 との互換性のために固定プロパティおよび 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 に設定します。プロトタイプバージョンを使用するには、両方のコンパイラーオプションを反対の値に設定します。 Flash Builder および Flash Professional のデフォルトのコンパイラー設定は、-as3 = true および -es = false です。

いずれかのコアクラスを拡張してメソッドのいずれかをオーバーライドする計画がある場合には、オーバーライドメソッドの宣言方法に AS3 名前空間がどのように影響する可能性があるかを理解しておく必要があります。 AS3 名前空間を使用する場合は、コアクラスメソッドのいずれのメソッドオーバーライドも、override 属性で AS3 名前空間を使用する必要があります。AS3 名前空間を使用せずにコアクラスメソッドをサブクラスで再定義する場合は、AS3 名前空間も override キーワードも使用しないでください。