akihiro kamijo: January 2006 Archives

« December 2005 | Main | February 2006 »

January 25, 2006

as 演算子

as 演算子は、ある値が特定の型に属することを保障したいときに使います。例えば、以下のような使い方です。

var myArray:Array = ["foo"];
var myString:String = myArray[0] as String;

見てのとおり括弧を使用したキャストとよく似ています。

var myArray:Array = ["foo"];
var myString:String = String(myArray[0]);

両者の違いが現れるのは、対象の値が目的の型にキャストできない場合です。括弧を使用したキャストではキャストできないと実行時エラーになります。一方、as 演算子の場合はキャストできないと式の値が null になります。

この動作は目的の型のオブジェクトのみを扱う処理を書く時に便利です。例えば、

for (i = 0; i < myArray.length; i++) {
  var myObj:MyComponent = myArray[i] as MyComponent;
  if (myObj) {
    // myObj の処理を行う
  }
}

のように記述すると、配列の要素が MyComponent に属する値でない場合、myObj が null になるため if ブロック内の処理をスキップさせることができます。

Posted by ackie at 10:25 AM | Comments (0)

January 24, 2006

is 演算子

is は AS3 から新しく追加された演算子で、ある変数が特定の型に属しているか判定するときに使用します。is の左側の変数が右側の型のメンバーであれば式の値は true に、そうでなければ false になります。

var mySprite:Sprite = new Sprite();
trace (mySprite is Sprite);           // true が出力される

AS2 では instanceof 演算子が同じ目的で使用されていましたが、AS3 ではサポートされていません。代わりに is を使用するようにしましょう。instanceof 演算子を使用すると正しく判定されないケースが出てきます。例えば、下の例では Sprite のインスタンスに対して、3種類の型に対して判定を行っています。

var mySprite:Sprite = new Sprite();
trace (mySprite is Sprite);           // true が出力される
trace (mySprite is DisplayObject);    // true が出力される
trace (mySprite is IEventDispatcher); // true が出力される

Sprite は DisplayObject のサブクラスで、かつ IEventDispatcher を継承した EventDispatcher クラスのサブクラスです。従って全てのケースで true になるはずです。ところが、 is の代わりに instanceof 演算子を使用すると、下の2つは false になってしまいます。

var mySprite:Sprite = new Sprite();
trace (mySprite instanceof Sprite);           // true が出力される
trace (mySprite instanceof DisplayObject);    // false が出力される
trace (mySprite instanceof IEventDispatcher); // false が出力され

基本データ型と is

数値型や文字列の値の型判定は、型情報がないので実際の値を評価する必要があります。データ型について詳しくはまだ取り上げていませんが、とりあえず、以下に代表的と思われる例に対する値を表にしましたので参考にしてください。

値 

String

Number

int

uint

Boolean

Object

"foo"

true

false

false

false

false

true

"1"

true

false

false

false

false

true

null

false

false

false

false

false

false

undefined

false

false

false

false

false

false

true

false

false

false

false

true

true

false

false

false

false

false

true

true

0

false

true

true

true

false

true

1

false

true

true

true

false

true

-1

false

true

true

false

false

true

1.0

false

true

false

false

false

true

NaN

false

true

false

false

false

true

Posted by ackie at 03:25 PM | Comments (0)

January 13, 2006

Namespace とステートパターン

今回は名前空間を使ってステートパターンにトライしてみたいと思います。基本となるのは、名前空間とステートを一対一に対応させようという考え方です。

以前 Sprite とマウスイベントという記事で Sprite でボタンが作れることに簡単に触れていますが、今回はそれをステートパターンで実装してみます。

では、ボタンとなるクラスの定義を始めましょう。とりあえず、ボタンに必要な状態の定義から始めます。

StatePatternButton.as:
public class StatePatternButton extends Sprite{
  // ボタンの状態を名前空間として3種類定義する
  private static namespace out;   // マウスがボタンの外にある
  private static namespace over;  // マウスがボタンの上にある
  private static namespace down;  // マウスがボタンの上で押された
 
  // それぞれの状態に応じた振舞いを定義
  out function handle(e:Event):Void {
    // out の状態での振舞いを記述
  }
 
  over function handle(e:Event):Void {
    // over の状態での振舞いを記述
  }
 
  down function handle(e:Event):Void {
    // down の状態での振舞いを記述
  }
}

今回は各ステートにおける振る舞いを一つのメソッドとして実装できると仮定しています。この部分はステート毎の差異しだいで変数の宣言になったり共通のインターフェースを継承したクラスになったりすることと思います。

次にステート変更用の手段を定義します。

  // 現在のステートを保持する変数
  private var currentState:Namespace;
 
  // 新しいステートを設定する関数
  private function changeState(newState:Namespace):Void {
    currentState= newState;
  }

現在のステートを currentState という変数に保持することにします。ステートの変更には changeState 関数を使用します。

さらに、マウスイベントの種類と名前空間(ステート)の関連を定義します。

  // 連想配列の作成
  private var stateMap:Object = new Object();
 
  // イベントの種類とステートの関連付け
  private function initStateMap():Void {
  	stateMap[MouseEventType.MOUSE_OUT] = out;
  	stateMap[MouseEventType.MOUSE_OVER] = over;
  	stateMap[MouseEventType.MOUSE_UP] = over;
  	stateMap[MouseEventType.MOUSE_DOWN] = down;
  }
 
  // イベントタイプに応じたステートを返す
  private function getState(e:Event):Namespace {
    // ほんとは引数や戻り値が null だった場合の考慮も必要
    return stateMap[e.type] as Namespace;
  }

というわけでマウスイベントハンドラの定義です。呼び出される処理はイベントの種類ではなくステートに応じて呼び出されるため、イベントハンドラは一つだけです。イベントハンドラからは handleEvent という関数を呼び出します。この関数の内部でステートに応じた処理を呼び出します。

  // イベントハンドラ(種類に関係なく使用される)
  private function onMouseEvent(e:Event):Void {
    var ns:Namespace = getState(e);
    changeState(ns);
    handleEvent(e);
  }
 
  // 現ステート用の処理を呼び出す関数
  private function handleEvent(e:Event):Void {
    currentState::handle(e);
  }

あとは、コンストラクタ内で必要な初期化を行えばおしまいです。

  // 初期状態のステートを定義
  private static var initState:Namespace = out;
 
  public function StatePatternButton() {
    // ボタンとして動作するように設定
    buttonMode = true;
 
    // イベント種類とステートの対応表作成
    initStateMap();
 
    // 関連するマウスイベントにリスナ関数を登録
    addEventListener(MouseEventType.MOUSE_OUT, onMouseEvent);
    addEventListener(MouseEventType.MOUSE_OVER, onMouseEvent);
    addEventListener(MouseEventType.MOUSE_UP, onMouseEvent);
    addEventListener(MouseEventType.MOUSE_DOWN, onMouseEvent);
 
    // ステートの初期化
    changeState(initState);
    // 他に必要な初期化があれば
    // initSomethingElse();
  }
}

イベントの種類とハンドラ名を関連付ける代わりに、イベントタイプと名前空間(=ステート)を関連付けてみました。MOUSE_UP と MOUSE_OVER に同じ関数名を関連付けるよりはちょっと気持ちいい感じです。

以下のようなクラスを作って実際に動かしてみましょう。

package {
  import flash.display.Sprite;
 
  public class StatePattenTest extends Sprite {
    public function StateTest() {
      addChild(new StateButton());
    }
  }
}

というわけで名前空間の使用例を紹介してみました。名前空間はなじみがないとなんの役に立つのか想像しにくいものです。とりあえずでも名前空間の使い方について考えるきっかけになればと思います。

Posted by ackie at 05:08 PM | Comments (0)

January 12, 2006

Namespace でのアクセス制御

前回の記事で public 宣言された関数を名前空間を使って上書きできる云々を書きましたが、このことから推測できるように public も名前空間です。実際に public の別名を作ることもできます。

public static namespace myNS1 = public;

public に限らずアクセス修飾子は全て名前空間として扱われます。この辺りから、なぜ EcmaScript4 のプロポーザルでは protected の実装が必要ないと判断されたかなんとなく想像できそうですね。

オープンな名前空間には、明示的に宣言していなくても public, internal, protected, private は予め含まれています。そのために public や private 宣言された属性の参照に public:: 等をつける必要が無かったわけです。

インスタンス属性の修飾名による参照

ここで、ちょっと寄り道をして、インスタンスの属性を参照するときの名前空間の指定方法について説明します。

まず、以下のような名前空間&クラス定義があるとします。

myNS1.as:
public namespace myNS1
 
myNS2.as:
public namespace myNS2
 
SampleClass.as:
public class SampleClass {
  myNS1 var foo:int = 0;
  myNS2 var foo:int = 1;
}

この場合 SampleClass のインスタンスから foo を参照する際は名前空間を意識しなくてはなりません。属性参照時に明示的に名前空間を指定するには以下のように記述します。

var sc:SampleClass = new SampleClass();
sc.myNS1::foo = 100;

また、名前空間の指定に変数を使用することもできます。変数の型は Namespace になります。

var sc:SampleClass = new SampleClass();
var ns:Namespace = myNS1;
sc.ns::foo = 100;

名前空間によるアクセス制御の例

さて、準備もできたので、ここで名前空間を使ったアクセス制御の例を紹介します。C++ のフレンド関数もどきの機能を実現してみたいと思います。

public class WithSecret {
  private static namespace mySecretNS;
  // アクセスを許可するクラス名を設定
  private var myFriend:Object = MyFriend;
 
  public function checkFriend(target:Object):Namespace {
    if(target is myFriend) {
      return mySecretNS;
    } else {
      return null;
    }
  }
  // プライベートな名前空間に関数を定義
  myNS function secretFunction():Void { 
    trace("this is my secret");
  } 
}

クラス WithSecret には mySecretNS という名前空間に定義した関数 secretFunction があります。mySecretNS はプライベートな名前空間なので、他のクラスから直接指定することはできません。つまり secretFunction は事実上「見えない」関数になっています。

一方、特定のクラス(この例では MyFriend クラス)のインスタンスに対してのみ mySecretNS を返す関数も実装されています。受け取ったインスタンスは mySecretNS を指定できるため、以下のようにして秘密の関数を呼び出すことができます。

public class MyFriend {
  public function getSecret(obj:WithSecret) {
    var ns:Namespace = obj.checkFriend(this);
    if(ns != null) {
      obj.ns::secretFunction();
    }
  }
}

Posted by ackie at 06:55 PM | Comments (0)

January 11, 2006

オープンな名前空間

open namespaces (とりあえず本記事内では「オープンな名前空間」と訳すことにします)という名前空間のリストがあります。これは、修飾子の付かない属性の参照が行われたとき(例えば ns1::foo ではなく foo)、属性を検索するのに使用されるリストです。リスト内のどの名前空間にも該当する属性名が含まれていなければその属性は参照不可と判断されます。

ある名前空間をオープンな名前空間に宣言するには use namespace を使います。

public static namespace myNS1;
public static namespace myNS2;
 
myNS1 var foo:int = 0;
myNS2 var foo:String = "zero";
 
use namespace myNS1  // NS1 をオープンな名前空間に宣言
trace(foo)           // 0 が出力される

この例では2つの名前空間に foo という属性が定義されていますが、myNS1 がオープンな名前空間に宣言されてから foo が参照されたため myNS1::foo の方が参照されることになります。

オープンな名前空間に候補となる属性が2つ以上見つかった場合はエラーになります。例えば、上記の例で myNS2 も宣言してから foo を参照すると2つの候補が見つかるため参照の解決が不能な状態になってしまいます。このような場合は myNS2::foo のように修飾名を使用する必要があります。

オープンな名前空間は、クラス定義や関数定義などのブロック単位で有効です。

オープンな名前空間の使用例

オープンな名前空間に明示的に宣言された名前空間は public より優先されます。つまり、同名の属性が2つの名前空間にあっても、片方が public だと public 宣言された属性は無視され曖昧な参照にはなりません。これを利用して、例えばデフォルトの動作が public 関数で実装されている場合のバージョンアップを、元の関数を書き換える代わりに新しく関数を追加する形で行うことができます。

以下は具体的なサンプルです。(ところで、アルファ版ではバグのために以下のコードは動きません。public が同列に扱われてしまうためです。アルファ版で動かすには public の代わりに version1 などの名前空間を使うことになります。)

まず、以下のクラスが最初のバージョンとして作成されていたとします。

SampleClass.as:
public class SampleClass {
  // デフォルトの関数を定義
  public function myFunc() {}
}

次に、バージョンアップした関数を version2 の名前空間に同じ識別子で定義します。

version2.as:
// 新バージョン名を名前空間として定義
public namespace version2
 
SampleClass.as:
public class SampleClass {
  // デフォルトの関数を定義
  public function sampleFunc() {}
  // 新しい振舞いの関数を追加定義
  version2 function sampleFunc() {}
}

SampleClass を上記のように変更しても従来から SampleClass を使用していたプログラムには影響しません。一方、version2 の名前空間をオープンな名前空間に宣言すれば新しく追加された関数を使用することができます。

MyClass.as:
public class MyClass {
  // version2 をオープンな名前空間に宣言
  use namespace version2
 
  public function myFunc() {
    var sc:SampleClass = new SampleClass();
    // version2::sampleFunc が呼ばれる
    sc.sampleFunc();
  }
}

名前空間を使ったバージョン管理の例でした。

Posted by ackie at 04:23 PM | Comments (0)

January 10, 2006

Namespace

クラスに名前をつけることは、クラス内で定義されているプロパティをクラスの名前空間に結びつける行為と考えることができます。パッケージやインターフェースについても同様です。これらの名前空間はプログラムの構造と固く結びついていますが、namespace を使うとプログラムの構造から独立して名前空間を扱えるようになります。例えば、任意の単位でクラスの振舞いを変えたり、実行時に動的に名前空間をプロパティと結びつけて可視/不可視をコントロールすることができます。

namespace の宣言

namespace はパッケージ内とクラス内で定義することができます。定義方法は基本的に変数の定義と同様です。

SampleClass.as:
package sample {
  public class SampleClass {
    // クラス内に名前空間を定義
    public static namespace myNSinClass;
  }
}
 
myNSinPackage.as:
package sample {
  // パッケージ内に名前空間を定義
  public namespace myNSinPackage;
}

パッケージレベルで public な名前空間を宣言するときは単独のファイルに記述します。public なリソースの宣言は一ファイルに付き一つのルールが適用されるためです。(EcmaScript プロポーザルによれば、クラス内の名前空間の宣言は static にする必要があるはずですが、現バージョンではインスタンス変数としても宣言できてしまいます。これは将来のバージョンでは修正されるものと思います)

名前空間には別名を作成したり明示的に値を与えることもできます。

// 匿名の名前空間を定義
public namespace myNS1;
// myNS1 の別名を定義
public namespace myNS2 = myNS1;
// 明示的に値を指定して名前空間を定義
public namespace myNS3 = "http://www.sample.com/namespace";

修飾名の使用

属性を定義する際に属性が所属する名前空間を指定するには、以下のように記述します。

public static namespace myNS1;
public static namespace myNS2;
myNS1 var foo:int = 0;
myNS2 var foo:String = "zero";

この例では foo という名前の属性が2つ定義されていますが、それぞれ別の名前空間に属しているため異なる属性として区別されます。参照する際は以下の形式で修飾名を記述します。

trace(myNS1::foo); // 0 が出力される
trace(myNS2::foo); // zero が出力される

また、属性定義時に複数の名前空間を指定することもできます。この場合、属性の実体はあくまで一つです。属性の別名を定義したと考えると良いでしょう。

myNS1, myNS2 var foo:int = 0;
trace(myNS1::foo); // 0 が出力される
trace(myNS2::foo); // 0 が出力される

属性の宣言時、namespace を使って定義された名前空間の指定はクラスのトップレベルの属性のみに適用可能です。メソッド内のローカル変数定義等には使用できません。

public class SampleClass {
  public static namespace myNS1;
 
  myNS1 var foo:int = 0; // OK
  public function myFunction():Void {
    myNS1 var bar:int = 0; // NG
  }
}

Posted by ackie at 05:44 PM | Comments (0)

January 06, 2006

静的なプロパティ

クラスには静的なプロパティとインスタンスプロパティを定義することができます。インスタンスプロパティはその名のとおりインスタンス毎に存在するプロパティですが、静的プロパティはインスタンスと関連付けられることは無く、代わりにクラスに一つだけ存在するクラスオブジェクトと結び付けられます。

静的変数

変数を宣言する際 static をつけると静的変数の宣言になります。

public static var myStaticVar:int;

静的変数はそれを宣言しているクラスまたはそのサブクラスからは直接参照することができます。

public class BaseClass {
  public static var myStaticVar:int;
  // クラス内で宣言されている静的変数にアクセス
  public function myBaseFunc():Void {
    myStaticVar = 0;
  }
}
 
public class ChildClass extends BaseClass {
  // 親クラスの静的変数にアクセス
  public function myChildFunc():Void {
    myStaticVar = 1;
  }
}

しかし、静的変数は継承されません。従って外部のクラスからは宣言されているクラスオブジェクトを通じてのみアクセスすることができます。

trace(BaseClass.myStaticVar);  // OK
trace(ChildClass.myStaticVar); // NG

継承されないということは、サブクラスに同じ名前で異なる型の静的変数を定義するのも可能ということです(クラスを使う側はちょっと混乱しそうですが)。また、静的変数と同名のインスタンス変数を定義することもできます。ますます混乱しそうなので、この辺りの説明は省略することにします。

静的関数

関数宣言時に static をつけると静的関数の宣言になります。

public static function myStaticFunction():Void;

もう何回も書いているのでそろそろしつこくなってきた感もありますが、関数もオブジェクトですから上記のセクションでの説明がそのまま当てはまります。補足しておきたい点は、

  1. 静的関数内からは静的に宣言されたプロパティしか参照することができません。
  2. 静的関数は継承されないので、サブクラスで同名の関数を宣言する場合でも override はつけません。シグニチャが一致する必要もありません。(むしろ関数である必要もありません)

Posted by ackie at 06:38 PM | Comments (0)

January 05, 2006

interface

AS2 では、パフォーマンスの観点からインターフェースの使用は控えるべきと言われていました。が、AS3 からはようやくその恩恵に与ることができそうです。実際、Flex2 のフレームワークでもインターフェースが多用されています。

インターフェースの定義

インターフェースは公開するメソッドの集合としての型を定義するのに使います。例えば、こんな感じです。

public interface ISample {
  function sampleFunc(arg:String = "hey"):Void; 
}

この例では、「sampleFunc という名前で hey という初期値の String 型の引数を一つ持ち返り値の型が Void である関数」を持つ型を定義し、それに ISample という名前をつけています。クラス定義と異なり、メソッドの定義だけが記述されています。メソッド本体はインターフェースを直接継承するクラス毎にオーバーライドして実装します。

public class SampleClass implements ISample {
  public function sampleFunc(arg:String = "hey"):Void {
    // メソッド本体を記述
  }   
}

インターフェースの継承は implements キーワードで宣言します。 このときインターフェースに定義されている全てのメソッドをオーバーライドする必要があります。オーバーライドするメソッドに public を宣言して、それ以外はインターフェース内の定義とまったく同じにします。

なお、静的メンバはインターフェースには定義できません。

インターフェースとプロパティ

以前に get と set についての記事で、アクセッサを定義すると関数名をプロパティのように扱えることを書きました。get も set も関数ですからインターフェースに定義することができます。つまり、実質的にインターフェースにもプロパティを定義できることになります。

ちょっと例を見てみましょう。

// インターフェースに get/set を定義
public interface IGetSet {
  function get myProp():String;
  function set myProp(val:String):Void;
}
 
// 上記のインターフェースを実装
public class SampleClass implements IGetSet {
  private var privateVar:String;
 
  public function get myProp():String {
    return privateVar;
  }
  public function set myProp(val:String):Void {
    privateVar = val;
  }
}

IGetSet を正しく実装したクラスでは以下のような操作ができるようになります。

var sc:SampleClass = new SampleClass():
sc.myProp = "hello";
trace(sc.myProp);   // hello が出力される

Posted by ackie at 05:25 PM | Comments (0)