« JSFLコマンド修正版 | メイン | FlashDevelop用Progressionプロジェクトテンプレート »

2008年8月12日

[as3] DisplayObject と Proxy

何かと AS3 の Proxy クラスは使えないという声をよく目にします。
特に目立つのが他のクラスを継承できないこと。

例えば DisplayObject の Proxy を作ったとしても、元がProxyクラスなので addChild() が使えないとか。たぶんそんなの。

今回はその辺について、ちょっと思ったことを休み前にまとめとこうかと。

Progression使いたいけど、自前で拡張したクラスも組み合わせて、 なんか有効にいろんなライブラリに手を出せないかな。と思ったのが きっかけでして、そこで一番に思い当たるのはやっぱりProxyクラスだったわけです。

で最初はProxy使った方法をいろいろごにょごにょと考えてたんですけど、 結局 addChild() や removeChild() で指定するためには、どうしても DisplayObject を 参照しなきゃいけなくなって、それが参照できる時点でProxyの役目は破綻してしまうんです。

var parentObject:DisplayObjectContainer
var childObject:Proxy

parentObject.addChild(childObject.display);

例えば上記の場合、親のDisplayObjectにProxyクラスが内包するDisplayObjectを displayというプロパティ経由で入れ子にしようとしています。

でも、これをしてしまうと、以降、親DisplayObjectからは本来隠蔽しておきたいはずの 子DisplayObjectを参照できるようになってしまい、 Proxyを通さずに直接参照・変更ができてしまいます。

そもそも Proxy は dynamic ステートメントを付けておかないと 効果を発揮しません。

つまり、頑張って DisplayObjectProxy とか EventProxy とかを作っても、 それを継承して様々なクラスに派生させていくような作り方だと、 全部ダイナミッククラスになってしまって はちゃめちゃになってしまいます。

もしこういう使い方をしたいならば、 素直にDisplayObject のサブクラスを作って、中身をすべて 内包する他のオブジェクトに委譲したほうがいいかもしれません。

ただ、DisplayObject で Proxy クラスを作成した場合でも、 結局のところ addChild には若干問題が残ります。

Sprite や MovieClip なら、委譲先のオブジェクトを DisplayObjectContainer から作成した Proxy 用クラスのコンテナ内に追加しておけば addChild() 時に画面上に現れますが、TextField や Bitmap は親オブジェクトになれないので、これらを継承したProxyクラスは addChild しても画面に表示されません。
あくまで追加されるのは Proxy クラスだからです。

addChild はまだマシな方で、ADDEDイベント発生時に内部処理で置き換えが可能ですが、 removeChild 時にはどう頑張ってもクライアント側で一工夫しないといけなくなります。

結局いろいろ試した結果、僕は addChild の問題は放棄しました。
Proxyを末端の子オブジェクトとして捉える限り、この問題は解決しないように思えたからです。

ProxyならProxyらしくコンテナ側に回してやればいいんじゃないかと。


で、以下はDisplayNodeというProxyクラスを継承したサブクラスを作ってみた場合の一例です。

var rootNode:DisplayNode = new DisplayNode(new Sprite());
rootNode.child1 = new Sprite();
rootNode.child1.grandchild1 = new Shape();
rootNode.child1.grandchild2 = new Shape();
rootNode.child1.grandchild3 = new Sprite();
rootNode.child1.grandchild3.addChild(new Bitmap());
rootNode.child2 = new Sprite();
rootNode.child2.dummy = new Sprite();
rootNode.child3 = new Sprite();
delete rootNode.child2;

trace(rootNode);

出力結果
instance148 [object Sprite]
  child1 [object Sprite]
    grandchild1 [object Shape]
    grandchild2 [object Shape]
    grandchild3 [object Sprite]
        instance153 [object Bitmap]
  child3 [object Sprite]

何をやってるかというと、
private var _target:DisplayObjectContainer;

override flash_proxy function getProperty(name:*):* 
{
    var child:DisplayObject = _target.getChildByName(name);
    if (child is DisplayObjectContainer) 
        return new DisplayNode(child as DisplayObjectContainer);
    return child;
}

override flash_proxy function setProperty(name:*, value:*):void 
{
    if (value is DisplayObject)
    {
        value.name = name;
        _target.addChild(value);
    }
}
setProperty と getProperty を使って表示オブジェクトを 参照名で addChild していく。ってもの。
child2 は delete の実行によって、内部で removeChild してます。


このクラスの中身も一応参考までにアップしておきます。
こういう使い方もあるってことで。

DisplayNode


DisplayObjectを継承したProxyを作りたいときも、なんとなくそれっぽいものまでは作る事ができます。
要はダイナミッククラスな訳ですから、以下のような処理で各メソッドの呼び出し時の処理に手を加えることも。

package 
{
import flash.display.DisplayObject;
import flash.display.Sprite;
import flash.utils.describeType;

dynamic public class TestProxy extends Sprite
{

public function TestProxy(target:DisplayObject) 
{
    setProxyMethods(target);
}

public function setProxyMethods(target:DisplayObject):void
{
    var data:XML = describeType(target);
    var n:uint, i:uint, name:String;
    var methods:XMLList = data.elements("method");
    n = methods.length();
    for (i = 0; i < n; i++ )
    {
        name = methods[i].@name;
        if (!this.hasOwnProperty(name))
        {
            this[name] = function():*
            {
                var func:Function = target[arguments.callee.methodName];
                return func.apply(target, arguments);
            }
            this[name].methodName = name;
        }
    }
    
    var accessor:XMLList = data.elements("accessor");
    n = accessor.length();
    for (i = 0; i < n; i++ )
    {
        name = accessor[i].@name;
        if (!this.hasOwnProperty(name))
        {
            this[name] = function():*
            {
                if (arguments.length == 1) 
                    return target[arguments.callee.propName] = arguments[0];
                return target[arguments.callee.propName];
            }
            this[name].propName = name;
        }
    }
}

}// end of class

}// end of package
var proxy:TestProxy = new TestProxy(new TextField());
proxy.appendText("hoge");
trace(proxy.text());          // 出力 : hoge
trace(proxy.getTextFormat()); // 出力 : [object TextFormat]

テキストフォーマットを吐き出すSpriteとか。

ただし、getter と setter は動的に追加できないから、やっぱり完全なProxyクラスの代用は難しいと思う。
上の例の場合はプロパティへのアクセス用メソッドを用意していて、text("hoge") みたいにして使う感じ。

TestProxy


結局のところ TestProxy みたいに無尽蔵にメソッドを追加しちゃうようなものは
実際にはあんまり使いどころがない気がする。コードヒントとか出ないし。

現在 DisplayObject を継承した Proxy 用クラスで、比較的使用頻度の高いのは以下のようなクラス。
こういうクラスを予め用意しておくと、結構役に立ってくれたりします。

SpriteProxy

このクラスはこのままインスタンスを作ってもまったく意味のないクラスなので継承しないと使えません。


これって Proxy じゃなくて Decorator な気もするけど、
その辺そんなに詳しいわけでもないので気にしない。



今回は DisplayObject と一緒に使う事に絞ってみたけど、 Proxy クラス自体はアクセス方法がかなり自由なのでいろんな使い方ができると思います。
中でも MXML みたいな使い方は得意なんじゃないかと。

あんまり多用すると後が怖いですけども。

それにしても、とりとめのない文章だなあ。

トラックバック(0)

このブログ記事を参照しているブログ一覧: DisplayObject と Proxy

このブログ記事に対するトラックバックURL: http://system.seyself.com/mt-tb.cgi/511

コメントする


画像の中に見える文字を入力してください。

しばらく時間が経過すると、システム内部と表示されている文字内容に食い違いが発生するようなので、
投稿する前にページをリロードすることをお勧めします。
リロードしてもフォームの内容は維持されます。