ActionScript 1.0使いのためのActionScript 2.0入門: Part 3
この一連の記事のPart2では、ActionScript 2.0のクラスの構造とシンタックスについて紹介してきた。その中で、パッケージ、public/private/static クラスメンバー、getterメソッドとsetterメソッド等の使い方を含む、ActionScript 2.0クラスの作成の基礎について見てきた。しかしまだ話していないことが二つある。継承とインタフェースについてだ。この3番目のパートではこれらのトピックについてそれぞれ堀り下げていく。
継承を理解する
継承はActionScript 2.0のクラスで初めて出てきた概念ではないが、あまり馴染みが無い方もいるだろう。ということで、すこし初歩的な説明をする必要があるようだ。
継承は、クラス間に存在する階層的な関係について使う言葉である。クラスは、スーパークラスのサブクラスとして定義することができる。つまり、サブクラスは、スーパークラスのメンバーを、明示的に再定義しなくとも全て自動的に継承するということだ。これはクラスに関する仕様として非常に重要な利点である。なぜならこのことによって、より高度なレベルでの抽象化が可能になるからだ。より高度なレベルでの抽象化とは、より効率的なワークフローを実現できることを意味する。
継承がどうして便利なのか、理解を助けるためにここで例を示そう:あなたが、この一連の記事のPart 2で作成したクラスによく似たCarクラスを作成したいと考えているとしよう。ここではあきらかに、Carはボート(Boat)、列車(Train)、飛行機(Plane)などを含むいろいろな型の乗物(Vehicle)のなかの一つである。全ての乗物はそれぞれが自分自身のクラスをもつに値するだけの十分な違いを持っているが、共通の特徴もいくつかもっている。前述の乗物群は、全て最大乗客数と運行距離数をあらわすプロパティを持っている。加えて、それらの乗物は移動が可能であり、従って与えられた速度でその乗物を動かすメソッドを作成することができる。
もしVehicleというスーパークラスを作成するとしたら、このクラスはCar, Boat, Train, Planeのようなクラスに共通の特徴や機能を全て持っていることになる。よって、それぞれのクラスで繰り返しコードを書くのではなく共通のコードを一箇所に定義しさえすれば良いはずだ。スーパークラスの作成には特別なテクニックは何も必要無い。これまで書いてきたクラスと全く同じように作成する。継承を設定するために必要になる追加テクニックはすべてサブクラスの中にある。
スーパークラスを拡張する
サブクラスをつくるには、Flashに対して、スーパークラスとして使用したいクラスが どれかを教えてやらなければならない。ActionScriptのOO(オブジェクト指向)用語ではこのことを、サブクラスがスーパークラスを拡張(extend)する、と言う。この例で言えば、Vehicleというスーパークラスを拡張したCarクラスが必要である。Flashに対してこのような 階層的関連を生成するよう伝えるには、以下のように、サブクラスの宣言でextendsキーワード を使えばよい:
class Subclass extends Superclass {
}
よって、CarとVehicleの例では、Carクラスの基本的な宣言はこのようになる:
class Car extends Vehicle {
}
CarクラスがVehicleクラスを拡張するように一度定義してしまえば、自動的に、Vehicleクラスのプロパティとメソッド全てがCarクラス内部からアクセス可能になる。
スーパークラスのメソッドの呼び出しとオーバーライド
スーパークラスのメソッドをスーパークラス内で実装されている
とおりそのままサブクラスに継承させたい場合がままある。このような場合は、それらの
メソッドに関してはサブクラス定義の中では何もする必要はない。
しかし、サブクラス内で、スーパークラスのメソッド中の一つに
特別な実装を定義したい場合もあるだろう。これをメソッドのオーバーライドと
いうが、こう呼ばれるのは、サブクラスが自分自身の実装を定義して、継承されたバージョンを
結果的に上書き(override)するからだ。
メソッドをオーバライドするばあい、いくつかの選択肢がある:
* スーパークラスの特定のメソッドの実装が全て無視されるようにそのメソッドを完全にオーバーライド
することができる。
* スーパークラスのメソッドも呼び出されるが、サブクラスの実装が機能を一部追加する
ようにメソッドをオーバーライドすることができる。
スーパークラスの特定のメソッドを完全にオーバーライドするためには、
サブクラスの中で同じ名前で、スーパークラスのメソッドに対する参照を
内部に持たないメソッドを定義するだけでよい。例えば、スーパークラスが
このようなメソッドを持っている場合:
// スーパークラスで定義されたメソッド
public function display():Void {
trace("This is the superclass method.");
}
以下のようにすれば、スーパークラスのメソッドを無視するような、サブクラスのメソッドの実装を定義できる:
// サブクラスで定義されたメソッド
public function display():Void {
trace("This is the subclass method.");
}
そうでなく、サブクラスのメソッドの実装を、スーパークラスの実装も呼び出すように
定義したい時は、スーパークラスを参照するsuperキーワードを使えば
明示的にスーパークラスのメソッドをサブクラスの実装の内部から呼び出すことができる。
例えば、サブクラス内のdisplay()メソッドの例は、以下のように書き換えることで、
スーパークラスの実装内容も呼び出すことになる:
// サブクラス内で定義されたメソッドがスーパークラスのメソッドも呼び出す
public function display():Void {
super.display();
trace("This is the subclass method.");
}
このdisplay()メソッドは、サブクラスのインスタンスから呼び出されると出力パネルに以下のように表示する:
This is the superclass method.
This is the subclass method.
サブクラス内ではどこでもsuper参照を使うことができる。例えば、super()を呼び出すと、このメソッドはスーパークラスのコンストラクタメソッドを参照する。これは、サブクラスのコンストラクタに対してそのサブクラスに特有の動作を実行させたいが、スーパークラスのコンストラクタにもいくつか特定の値を渡して呼び出したい場合に有効である。一つだけ注意する必要があるのは、スーパークラスのコンストラクタを明示的に呼び出す場合は常にサブクラスのコンストラクタの中の第一行目で呼び出さなければいけないということだ。そうしないとコンパイルエラーが出てしまう。
VehicleクラスとCarクラスを作成する
理論についてはここまでである程度読んでもらったと思うので、今度はこの理論を簡単な実践例に適用していこう。この例では、これからVehicleクラスとCarサブクラスを作成することになる。この一連の記事の中のPart 2の例で練習が終っているなら、この実例の機能を辿って行くのはかなり楽なはずだ。Part 2の実例演習をまだ終えておらずこの例の機能を見て少し混乱してしまうということなら、Part 2の例を復習するといいだろう。
1. まだやっていないなら、自分のFlashのクラスパスにある
ディレクトリの中に com.person13 パッケージ用のディレクトリ構造を作成する。
例えば、D:\ActionScriptClasses がFlashのクラスパスなら、
D:\ActionScriptClasses\com\person13 を作成する。
2. 新規のActionScriptファイルを開く
3. ファイルに以下のコードを追加する:
class com.person13.Vehicle {
private var _nPassengers:Number;
private var _nMiles:Number;
private var _nInterval:Number;
function Vehicle(nPassengers:Number, nMiles:Number) {
_nPassengers = nPassengers;
miles = nMiles;
}
public function get passengers():Number {
return _nPassengers;
}
public function get miles():Number {
return _nMiles;
}
public function set miles(nMiles:Number):Void {
if(nMiles >= 0) {
_nMiles = nMiles;
}
else {
_nMiles = 0;
}
}
public function move(nMPH:Number):Void {
if(nMPH == null || nMPH <= 0) {
clearInterval(_nInterval);
}
else {
_nInterval = setInterval(this, "increment", 1000, nMPH);
}
}
private function increment(nMPH:Number):Void {
_nMiles += nMPH;
}
}
4. ファイルをcom/person13ディレクトリにVehicle.asという名前で保存する
5. この一連の記事の中のPart 2にある実例演習を終えているなら、既に自分で作ってあるCar.asファイルを開く(あらかじめcom/person13ディレクトリに存在している必要がある)。まだ終っていないなら、新規にActionScriptファイルを開く。
6. コードを修正するかもしくは以下のコードをファイルに追加する。:
// CarクラスはVehicleを拡張することにより、スーパークラスの
// プロパティとメソッドを全て継承する。
class com.person13.Car extends com.person13.Vehicle{
// Carクラスに特有のプロパティを宣言する。他のプロパティは継承されるので
// 再度宣言する必要はない。
private var _sMake:String;
private var _sModel:String;
private var _nYear:Number;
function Car(sMake:String, sModel:String, nYear:Number, nPassengers:Number,
nMiles:Number) {
// スーパークラスのコンストラクタを呼び出し、Carクラス特有のプロパティに
// 値を代入する。
super(nPassengers, nMiles);
_sMake = sMake;
_sModel = sModel;
year = nYear;
}
public function get make():String {
return _sMake;
}
public function get model():String {
return _sModel;
}
public function get year():Number {
return _nYear;
}
public function set year(nYear:Number):Void {
if(nYear >= 1886) {
_nYear = nYear;
}
else {
_nYear = 1886;
}
}
// Vehicleクラスから継承したmove()メソッドを呼び出すだけの、
// drive()というメソッドを定義する。こうすることが必要というわけではないが、
// driveという言葉はmoveという汎用的な言葉よりも
// 車(car)により強く関連した言葉なので、Carクラス用に
// より直感的なAPIを宣言するという意味でこうしている。
public function drive(nMPH:Number):Void {
move(nMPH);
}
}
7. Vehicle.asを保存したcom/person13 ディレクトリにファイルを保存する。ファイルが新規に作られたものなら(Part 2で練習として作ったCar.asを修正しているのでなければ)そのファイルをCar.asという名前にする。新規に作られたものでなければファイルは既にCar.asという名前になっているはずだ。
8. Part 2の実例演習が終っているなら、そのサンプルをテストするために使ったのと同じ.flaファイルを開く。終っていなければ新規に.flaファイルを開く。
9. 前の実例演習で.flaが既に完成しているならコードを修正する必要はない。新規に.flaを作成したのであれば、デフォルトレイヤーの第一フレームに以下のコードを
追加する:
import com.person13.*;
function displayMileage(carObj:Car):Void {
trace(carObj.miles);
}
var car:Car = new Car("Oldsmobile", "Alero", 2000, 5, 43000);
car.drive(65);
setInterval(displayMileage, 100, car);
10. ムービーをテストする。milesの値がほぼ毎秒インクリメントしながら出力パネルに継続して表示されるのがわかるはずだ。
[以下はオプション]
11. 新規にActionScriptファイルを開く。
12. 以下のコードをファイルに追加する:
class com.person13.Plane extends com.person13.Vehicle{
private var _sMake:String;
private var _sModel:String;
function Plane(sMake:String,
sModel:String,
nPassengers:Number,
nMiles:Number) {
super(nPassengers, nMiles);
_sMake = sMake;
_sModel = sModel;
}
public function get make():String {
return _sMake;
}
public function get model():String {
return _sModel;
}
public function fly(nMPH:Number):Void {
move(nMPH);
}
}
13. ファイルをcom/person13ディレクトリにPlane.asという名前で保存する
14. 新規に.flaファイルを開く
15. デフォルトレイヤーの第一フレームに以下のコードを追加する。:
import com.person13.*;
function displayMileage(obj:Plane):Void {
trace(obj.miles);
}
var plane:Plane = new Plane("Boeing", "747", 300, 100000);
plane.fly(400);
setInterval(displayMileage, 100, plane);
16. ムービーをテストする。
サブクラスを拡張する
継承は一段階だけに制限されているわけではない。サブクラスも拡張できるし、さらに言えばサブクラスのサブクラスも拡張できる。例えば、Car(車)クラスを拡張するFerrari(フェラーリ)クラスや、Plane(飛行機)クラスを拡張するLear(リアジェット)クラスを作ることもできる。クラスを拡張する場合は、スーパークラスが他のクラスのサブクラスであるかどうかに関わらず、プロセスは同じである。
インタフェースを理解する
インタフェースはおそらく最も理解されていないプログラミング概念の一つだろう。インタフェースは、単純化して言えば、クラスが従うべきルールの集合だ。あるインタフェースのルールを満たすようにクラスを定義する場合、クラスはそのインターフェスを実装(implement)している、という言い方をする。
それでは、インタフェースはどのような形のルールあるいはガイドラインを定義しているのだろうか? 具体的に言うと、ActionScript 2.0のインタフェースは、Flashに対して、あるクラスがどのようなメソッドを実装(implement)しなければならないかを伝える。そこには以下のような情報が含まれる:
* メソッドの名前
* 要求されるパラメータ
* publicなメソッドの宣言(インタフェース内ではprivateやstaticのメソッドの宣言はできない)
* 返り値の型
以下に示すものはインタフェースに含まれない:
* プロパティ宣言
* メソッドの実装
* privateやstaticなメソッドの宣言
このように、クラスはインタフェースを実装(implement)できる。クラスがインタフェースを実装(implement)する場合、そのクラスは、インタフェースで宣言されたメソッドに一致するメソッドを必ず持つことになっている。一致するメソッドが含まれていない場合は、Flashはコンパイル時にエラーを生成する。
インタフェースの利点
開発者はインタフェースの潜在的な利点を最初は理解しないことが多い。結局のところ、インタフェースを使うことで何が得られるのだろうか?最初のうちは、とくに実質的な見返りのない余計な努力が要るように見える。インタフェースは、中で宣言しているメソッドをどれも実際には実装していないので、特定の機能を提供してくれるわけではない。
インタフェースの本当の利点はかなり微妙なもので、その利点を認識するには一歩後ろに下がってより大きな視点でとらえる必要がある。一つのアプリケーションのために一つクラスを作成しているだけなら、そのクラスのためにインタフェースを書くことに全く意味はない。また、全てのクラスがインタフェースを実装(implement)する必要もない。しかし、より大きなスコープ - より長いタイムフレーム、より大きなプロジェクトより大きなチーム - の中で作業している場合は、インタフェースは基本的な青写真を描くための役に立ち、そうなれば、クラスを開発する場合だけでなくクラスを利用する場合にもワークフローを改善することができるだろう。
例えば、Vehicle(乗物)、Animal(動物)、Wind(風)、Planet(惑星)といったクラス群をチームで作成しているとしよう。これらのクラスはそれぞれ異なるAPIを持っているが、どれにも共通していることが一つある - どれも動く(move)ことができるということだ。「動き」が、上記の各クラスに実装したいものであるなら、その全てのクラスが実装することができるIMoveableというインタフェースを作成すればよい。IMoveableインタフェースはmove()メソッドを宣言し、IMovableを実装(implement)しているクラスはその時点でmove()メソッドを持っている必要があることになる。これは、チーム開発において有効だ。インタフェースを作成してそれをどのクラスが実装するかを決定した場合、そのクラスで作業をしている開発者は既にどのメソッドを書くべきかを知っていることになり、クラスのAPI間で標準化を行うことができる。他には、クラスを利用する開発者にとっての利点もある。クラスを使用する開発者として、そのクラスがある特定のインタフェースを実装していることがわかれば、そのクラスのAPIについて既にある程度わかっていることになる。
一つのクラスは複数のインタフェースを実装することができる。この理由により、インタフェースは実際の多重継承をサポートしていない言語で多重継承を行う方法として良く使われる。ActionScriptでは、クラスは他のクラスを一つしか拡張できない。例えば、CarクラスはVehicleクラスを拡張しているので、他のクラスを拡張できない。しかしCarクラスは複数のインタフェースを(Vehicleクラスを拡張するような形で同時に)実装できる。例えば、CarクラスはIDrivable, IManufacturable, ISellableといったインタフェースを実装できる。
インタフェースを作成する
ここまでで理論については読んでいただけたと思うので、今度はインタフェースの宣言と定義を見ていこう。クラスのシンタックスと構造についてはもうわかっていると思うので、新しく習得する必要のあることはそれほどないだろう。
インタフェースの宣言はクラスの宣言と見た目はほとんど同じだ。唯一の違いは、インタフェースの宣言はclassキーワードの代わりにinterfaceキーワードを使うということだ。例えば:
interface IMoveable {
// インタフェースの定義
}
インタフェースは、それを実装するクラスがどのメソッドを持っている必要があるか、ということだけを指定する。よって、インタフェースにはプロパティを宣言できない。そしてインタフェースにはメソッド内容を定義/実装してはならない。インタフェースの各メソッドの宣言は以下に挙げる要素だけから構成されている(前述のものを再掲):
* メソッド名
* 要求されるパラメータ
* メソッドのpublic宣言
* 返り値型
これが完全なIMoveable インタフェースの例である::
interface IMoveable {
public function move(nMPH:Number):Void;
}
クラスの場合と同じく、インタフェースをパッケージすることもできる。例えば、IMoveableをcom.person13にパッケージしたいなら、以下のように宣言する:
interface com.person13.IMoveable {
public function move(nMPH:Number):Void;
}
また、インタフェースもクラスと同じように拡張できるので、例えば、IMoveableインタフェースを拡張したIDrivableインタフェースを作るとしたら、以下のようになるだろう:
import com.person13.IMoveable
interface com.person13.IDrivable extends IMoveable {
public function drive(nMPH:Number):Void;
}
インタフェースを実装する
一度インタフェースを定義したら、implementsキーワードの後にコンマをデリミタにしてクラスに実装させたいインタフェースのリストを列挙したものを付けることで、クラスに対してそのインタフェースを実装してもらいたいということを伝えることができる。例えば、VehicleクラスにIMoveableインタフェースを実装してもらいたいならクラス宣言の最初の部分はこのようになる:
class com.person13.Vehicle implements com.person13.IMoveable {
あるいは、import文を使って最初にインタフェースをインポートすることももちろん可能だ:
import com.person13.IMoveable;
class com.person13.Vehicle implements IMoveable {
仮想のシナリオとして、CarクラスがIDrivable, IManufacturable, ISellableといった複数のインタフェースを実装することを考えた場合、クラス宣言の最初の部分はこのようになるだろう:
import com.person13.*;
class com.person13.Car implements IDrivable, IManufacturable, ISellable {
クラスがインタフェースを実装する場合、Flashはコンパイル時にそのクラスが全ての正しい実装を行っているかどうかをチェックする。もしあるメソッドが欠けていたりインタフェースを遵守しない形で間違って宣言されていれば、Flashはエラーメッセージを表示して知らせてくれる。
結び
これでこの一連の記事の3番目が終った。ここで扱われていることについて質問やコメントがあれば何でも言ってほしい。次の記事では、try、catch、finally 、throwを使ったエラー処理 - ActionScript 2.0のもう一つの新機能 - について説明するので待っていてほしい。
いつも読んでくれてありがとう。
元URLはhttp://www.person13.com/articles/as2primerpartthree/です。
日本語訳は、原著者であるJoey Lott氏の許可を得てFACEs Projectが行いました。