2011年1月7日金曜日

Flash IDE 上でのMVCについて考えてみた(その2)

GUIコンポーネントは入れ子の構造となり、また各コンポーネントは MVC のすべての役割をもつため、MVC という枠組みだけでは対応できないと考え、前回の内容を変更しました。

今回作成したサンプル
  • TextArea に最初 0 が表示され、マウスでクリックもしくは Enter キーを押すごとに値が 1 増えるアプリです。

シンボル
  • LIBRARY 内のフォルダ名
    • com.objectfanatics.samplecomponent
  • シンボル名
    • Component
      • リンク先のクラス
        • com.objectfanatics.samplecomponent.Component
クラス構成
  • com.objectfanatics.samplecomponent パッケージ
    • com.objectfanatics.samplecomponent.Component
      • Flash IDE 上で作成したシンボルからリンクされるクラス。
      • コンポーネント全体を表すクラス。
  • com.objectfanatics.samplecomponent.model パッケージ
    • com.objectfanatics.samplecomponent.model.Model
      • コンポーネントのモデルのインタフェース。
      • モデルは可能な限り実装を意識しないで設計したいため、あえてインタフェースを作成。
    • com.objectfanatics.samplecomponent.model.ModelImpl
      • コンポーネントのモデルの実装。
    • com.objectfanatics.samplecomponent.model.ModelListener
      • Model の変更内容を監視するためのリスナーインタフェース
  • com.objectfanatics.samplecomponent.view パッケージ
    • com.objectfanatics.samplecomponent.view.ModelListenerImpl
      • Model の変更を監視し、Model の変更内容に応じて View を更新するクラス。
      • ModelListenerインタフェースの実装クラス。
    • com.objectfanatics.samplecomponent.view.ViewInitializer
      • View を初期化するためのクラス。
  • com.objectfanatics.samplecomponent.controller パッケージ
    • com.objectfanatics.samplecomponent.controller.Controller
      • Controller の Model 寄りの処理を行なう。
    • com.objectfanatics.samplecomponent.controller.ViewAdapter
      • Controller の Component 寄りの処理を行なう。
      • Component の内部要素に対してリスナーを登録し、イベントを監視する。
      • Component からのイベントを処理し、必要に応じて Model を更新する。
ソース
package com.objectfanatics.samplecomponent.component {

import com.objectfanatics.samplecomponent.controller.Controller;
import com.objectfanatics.samplecomponent.controller.ViewAdapter;
import com.objectfanatics.samplecomponent.model.Model;
import com.objectfanatics.samplecomponent.model.ModelImpl;
import com.objectfanatics.samplecomponent.view.ModelListenerImpl;
import com.objectfanatics.samplecomponent.view.ViewInitializer;

import flash.display.Sprite;
import flash.text.TextField;

/**
 * FlashIDE 上の LIBRARY にて、フォルダ名が com.objectfanatics.samplecomponent、名前が Component のシンボルに対応するクラスです。
 *
 * このサンプルでは、GUIコンポーネントという単位を扱い、このクラスが1つの GUI コンポーネントを表しています。
 *
 * このコンポーネントの内部には TextField などの GUI コンポーネントが内包(FlashIDE上で定義されたシンボルのインスタンスやActionScriptで動的に作成されたものなど)
 * されており、それはこのコンポーネントそのものの Model, View, Controller のいずれでもないと考えます。
 *
 * そのため、このコンポーネントは Model, View, Controller だけでなく、自身を表す Component とその内部の Sub Components で表されると考えます。
 *
 * View については、Model の内容を常に反映しながら情報を外部に提供するという MVC で言うところの View は、オブジェクトとしては存在しないと考えます。
 * なぜなら、この役割は自身を表す Component もしくはその内部の Sub Components によって達成されるからです。
 *
 * 自身を表す Component とその内部の Sub Components が View に該当するかというと、View としての性質を持っていますが、同時に Model でもあり Controller でも
 * あるため、View と表現するのは妥当ではないと考えます。そのため、自身を表す Component とその内部の Sub Components という表現をしています。
 *
 */
public class Component extends Sprite {

    /**
     * numberText sub component.
     */
    public var numberText:TextField;

    public function Component ():void
    {
        // Model を初期化します
        var model:Model = new ModelImpl ();

        // Model の変更内容を監視し Component に反映させるための ModelListener を初期化します
        model.modelListener = new ModelListenerImpl (this);

        // Controller を初期化します
        var controller:Controller = new Controller (this, model);

        // Component もしくは Sub Components からの入力を監視し Controller に伝える処理を行うクラスを初期化します
        new ViewAdapter (this, controller);

        // View を初期化します
        new ViewInitializer (this, model);
    }
}
}

package com.objectfanatics.samplecomponent.controller {

import com.objectfanatics.samplecomponent.component.Component;
import com.objectfanatics.samplecomponent.model.Model;

/**
 * Controller クラスです.
 *
 * Component の監視処理の記述は ViewAdapter クラスが行っています。
 *
 * Controller 自身が Component や Sub Component の監視を行う場合、複数のコンポーネント上で
 * ボタンの監視やキーが押されることが、Model の値をインクリメントさせるトリガに成り得ます。
 * このような処理を Controller の Component 寄りの処理と言うことにします。
 *
 * しかし、どのボタンが押されてもキーが押されても Controller としては Model の値をインクリメント
 * して欲しいというイベントであると考えられます。このような考えに基づく処理を Controller の
 * Model 寄りの処理と言うことにします。
 *
 * そう考えると、Controller の Model 寄りの処理を Controller が、Component 寄りの処理を
 * ViewAdapter が行っていると言うことができます。
 */
public class Controller {

    private var model:Model;
    private var component:Component;

    public function Controller (component:Component, model:Model):void
    {
        this.component = component;
        this.model = model;
    }

    /**
     * Model の値をインクリメントします.
     */
    public function increment ():void
    {
        model.increment ();
    }
}
}

package com.objectfanatics.samplecomponent.controller {

import com.objectfanatics.samplecomponent.component.Component;

import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.ui.Keyboard;

/**
 * Controller の Component 寄りの処理を受け持つクラスです.
 *
 * Component もしくは Sub Components からの入力を監視し Controller に伝える処理を行います.
 */
public class ViewAdapter {

    private var controller:Controller;

    public function ViewAdapter (component:Component, controller:Controller):void
    {
        // Controller の格納
        this.controller = controller;

        // Component のクリックの検出設定
        component.addEventListener (MouseEvent.CLICK, onClicked);

        // Enter キーの検出設定
        component.addEventListener (KeyboardEvent.KEY_UP, onKeyUp);
    }

    private function onClicked (e:MouseEvent):void
    {
        controller.increment ();
    }

    private function onKeyUp(e:KeyboardEvent):void {
        if (e.keyCode == Keyboard.ENTER) {
            controller.increment ();
        }
    }
}
}

package com.objectfanatics.samplecomponent.model {

/**
 * Model インタフェースです.
 */
public interface Model {

    /**
     * ModelListener をセットします。
     * @param modelListener ModelListener
     */
    function set modelListener (modelListener:ModelListener):void;

    /**
     * 値を返します。
     * @return 値
     */
    function get number ():uint;

    /**
     * 値を1増やします。
     */
    function increment ():void;
}
}

package com.objectfanatics.samplecomponent.model {

/**
 * Model の実装クラスです.
 */
public class ModelImpl implements Model {

    private var _modelListener:ModelListener;

    /**
     * 保持される値
     */
    private var _number:uint = 0;

    /**
     * Model#get number():uint の実装
     */
    public function get number ():uint
    {
        return _number;
    }

    /**
     * Model#set modelListener(modelListener:ModelListener):void の実装
     */
    public function set modelListener (modelListener:ModelListener):void
    {
        this._modelListener = modelListener;
    }

    /**
     * Model#increment():void の実装
     */
    public function increment ():void
    {
        this._modelListener.numberChanged (++_number);
    }
}
}

package com.objectfanatics.samplecomponent.model {

/**
 * Model の変更内容を監視するためのリスナー
 */
public interface ModelListener {

    /**
     * 値が変更された場合に呼び出されます。
     *
     * @param number 変更後の値
     */
    function numberChanged (number:uint);
}
}

package com.objectfanatics.samplecomponent.view {

import com.objectfanatics.samplecomponent.component.Component;
import com.objectfanatics.samplecomponent.model.ModelListener;

/**
 * Model の変更内容を監視し Component に反映させるための ModelListener の実装クラス。
 */
public class ModelListenerImpl implements ModelListener {

    /**
     * 変更対象の Component
     */
    private var component:Component;

    /**
     * コンストラクタ
     * @param view 変更対象の View
     */
    public function ModelListenerImpl (component:Component):void
    {
        this.component = component;
    }

    /**
     * Model の値が変更された時に呼び出され、View 上の表示を変更します。
     * @param currentNumber 現在のの Model の値
     */
    public function numberChanged (currentNumber:uint)
    {
        component.numberText.text = "" + currentNumber;
    }
}
}

package com.objectfanatics.samplecomponent.view {

import com.objectfanatics.samplecomponent.component.Component;
import com.objectfanatics.samplecomponent.model.Model;

/**
 * View を初期化するためのクラスです.
 *
 * ここではこのコンポーネント全体の見た目を初期化するため、View ということばをあえて使用しています。
 */
public class ViewInitializer {

    /**
     * View を初期化します.
     *
     * @param component Component
     * @param model Model
     */
    public function ViewInitializer (component:Component, model:Model):void
    {
        component.numberText.text = "" + model.number;
    }
}
}

今回の所感

今回 Component として扱った箇所を前回は View として扱っていてなんとなく気持ち悪かったのですが、なんとなくすっきりした気がします。

コンポーネントを構成するクラスの数がかなり多くなっていますが、1つのコンポーネントのサブコンポーネントが数十とか数百あり全体のソースが数千行以上に及ぶようなコンポーネントをを作成するのであればこの程度の分割はメリットはあってもデメリットはないと思います。

それほど大きなコンポーネントを作成しない場合であれば、いくつかもしくはすべてを省略してもかまわないと思います。要はケースバイケースということですね。

Twinner で画面上のコンポーネントを動かしたりするような場合、処理はどこに書くのかな? その処理のトリガがGUIからの入力であればトリガの処理は ViewAdapter に記述して、実際の処理は Controller かな。Twinnerなどによる処理や ActionScript を用いてのコンポーネントの処理などが多くデザイン系の比重が大きい場合は、Controller の描画寄りの処理用として別クラスにしたほうがいいかもしれません。しかし、クラス名や責務はなんだろう。Controllerなのか?Viewなのか?

徹夜後の夕方なのでちょっとヘロヘロです(´・ω・`)

アーカイブ

2011年1月1日土曜日

asdocのエラー

asdoc を作成する際に遭遇したエラーのメモ

エラー内容
[Fatal Error] :3:6: エレメント型 "Hoge" は対応する終了タグ "</Hoge>" で終了する必要があります。
適格な形式でないテキストが検出されました。詳細については、C:\hoge\fuga\piyo\validation_errors.log を参照してください。

ログ内容
org.xml.sax.SAXParseException: エレメント型 "Hoge" は対応する終了タグ "</Hoge>" で終了する必要があります。

原因と対応方法
メソッドの /** */ で囲まれているドキュメント内に Vector.<Hoge> のような箇所があったことが原因。括弧をエスケープして対応。

2010年12月1日水曜日

asdocでjavadocの-linkオプションのようなことをしようと思ったが、、、

自作ライブラリの asdoc から as3 の core に含まれている Number とか Boolean とか Vector などの asdoc へのリンクを貼りたくて、調べてみました。

すると、同様の要求が下記のように登録されていました。

ASDoc: Hyperlinks to Actionscript Language Reference documentation
For example, if a method in my custom class returns a Boolean, I would like to be able to click on the word "Boolean", which would take me to the Boolean documentation in the AS3.0 Language Reference:
http://help.adobe.com/en_US/AS3LCR/Flash_10.0/Boolean.html
まさにこの機能! (`・ω・´)

しかし、type が Minor Enhancement として登録されています。

マイナーらしいです。 (´・ω・`)

しかも、
Nice request but out of scope for this release. Deferring.
として、クローズされてます。

as3commonsのasdocでも対応してないところをみると、今のところ有効な手段はないかもしれませんね。

2010年11月27日土曜日

flex-mojos と asdoc

公式ページ
http://flexmojos.sonatype.org/

flexmojos-maven-plugin のある場所
http://repository.sonatype.org/content/groups/flexgroup/org/sonatype/flexmojos/flexmojos-maven-plugin/

com.adobe.flex.framework系のある場所
以下あたりを参考にして、バージョンを決めればよさそう。
https://repository.sonatype.org/content/groups/flexgroup/com/adobe/flex/framework/framework/

mvn test と mvn verify ができる最低限の pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    
    <!-- pom.xml参考 : http://dev.yoolab.org/maven/content/repositories/releases/org/as3commons/as3commons-project/1.0.1/as3commons-project-1.0.1.pom -->
    
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.objectfanatics</groupId>
    <artifactId>sandbox</artifactId>
    <packaging>swc</packaging>
    <name>ObjectFanatics Sandbox Project</name>
    <version>0.0.1-SNAPSHOT</version>
    <inceptionYear>2010</inceptionYear>
    <url>http://www.objectfanatics.com/</url>
    <description>ObjectFanatics Sandbox Project</description>
    
    <properties>
        <!-- flex-mojos version -->
        <flex-mojos.version>3.5.0</flex-mojos.version>

        <!-- flex version -->
        <flex.version>3.5.0.12683</flex.version>

        <!-- flashplayer version -->
        <flashplayer.version>${flashplayer.version.major}.${flashplayer.version.minor}.${flashplayer.version.revision}</flashplayer.version>
        <flashplayer.version.major>10</flashplayer.version.major>
        <flashplayer.version.minor>0</flashplayer.version.minor>
        <flashplayer.version.revision>0</flashplayer.version.revision>
    </properties>
    
    <!-- 開発者情報 -->
    <developers>
        <developer>
            <id>makoto.sato</id>
            <name>Makoto Sato</name>
            <email>makoto.sato [at] objectfanatics.com</email>
            <url>http://www.objectfanatics.com</url>
            <organization>ObjectFanatics Ltd.</organization>
            <organizationUrl>http://www.objectfanatics.com</organizationUrl>
            <timezone>+9</timezone>
        </developer>
    </developers>

    <dependencies>
        
        <!-- Player/Air Global dependency. -->
        <dependency>
            <groupId>com.adobe.flex.framework</groupId>
            <artifactId>playerglobal</artifactId>
            <version>${flex.version}</version>
            <scope>external</scope>
            <type>swc</type>
            <classifier>${flashplayer.version.major}</classifier>
            <!--<classifier>${flashplayer.version.major}.${flashplayer.version.minor}</classifier>-->
        </dependency>
        
        <!-- for unit testing. http://asunit.org/ -->
        <dependency>
            <groupId>com.asunit</groupId>
            <artifactId>asunit</artifactId>
            <version>20071011</version>
            <type>swc</type>
            <scope>test</scope>
        </dependency>
        
        <!-- Dependencies for Flex. This includes flex classes. ex: ListCollectionView. -->
        <dependency>
            <groupId>com.adobe.flex.framework</groupId>
            <artifactId>framework</artifactId>
            <version>${flex.version}</version>
            <scope>external</scope>
            <type>swc</type>
        </dependency>
        
        <!-- Dependencies for Flex. This includes resource bundle. Not having this dependency causes some errors. ex: [ERROR] Unable to resolve resource bundle "collections" for locale "en_US". -->
        <dependency>
            <groupId>com.adobe.flex.framework</groupId>
            <artifactId>framework</artifactId>
            <version>${flex.version}</version>
            <type>rb.swc</type>
            <scope>external</scope>
        </dependency>

        <dependency>
            <groupId>com.adobe.flex.framework</groupId>
            <artifactId>utilities</artifactId>
            <version>${flex.version}</version>
            <type>swc</type>
            <scope>external</scope>
        </dependency>

        <dependency>
            <groupId>com.adobe.flex.framework</groupId>
            <artifactId>flex</artifactId>
            <version>${flex.version}</version>
            <type>swc</type>
            <scope>external</scope>
        </dependency>

        
        <dependency>
            <groupId>com.adobe.flex.framework</groupId>
            <artifactId>framework</artifactId>
            <version>${flex.version}</version>
            <scope>external</scope>
            <type>zip</type>
            <classifier>configs</classifier>
        </dependency>
        
        <dependency>
            <groupId>com.adobe.flex.framework</groupId>
            <artifactId>rpc</artifactId>
            <version>${flex.version}</version>
            <scope>external</scope>
            <type>swc</type>
        </dependency>
        
        <dependency>
            <groupId>com.adobe.flex.framework</groupId>
            <artifactId>rpc</artifactId>
            <version>${flex.version}</version>
            <type>rb.swc</type>
            <scope>external</scope>
        </dependency>

    </dependencies>
    
    <build>
        <sourceDirectory>src/main/actionscript</sourceDirectory>
        <testSourceDirectory>src/test/actionscript</testSourceDirectory>
        <defaultGoal>test</defaultGoal>

        <plugins>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <configuration>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.sonatype.flexmojos</groupId>
                <artifactId>flexmojos-maven-plugin</artifactId>
                <version>${flex-mojos.version}</version>
                <extensions>true</extensions>
                <dependencies>
                    <dependency>
                        <groupId>com.adobe.flex</groupId>
                        <artifactId>compiler</artifactId>
                        <version>${flex.version}</version>
                        <type>pom</type>
                    </dependency>
                </dependencies>
                <configuration>
                    <configurationReport>true</configurationReport>
                    <targetPlayer>${flashplayer.version}</targetPlayer>
                    <locales>
                        <param>en_US</param>
                    </locales>
                    <updateSecuritySandbox>true</updateSecuritySandbox>
                    <includeAsClasses>
                        <source>
                            <directory>src/main/actionscript</directory>
                        </source>
                    </includeAsClasses>
                </configuration>
                </plugin>
                <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-site-plugin</artifactId>
            </plugin>
        </plugins>

    </build>
    
    <repositories>
        <repository>
            <id>flex-mojos-repository</id>
            <url>http://repository.sonatype.org/content/groups/flexgroup</url>
        </repository>
    </repositories>
    
</project>

asDocの生成
flex-mojosでやろうとしたが、3系だとVectorとかのクラスを認識せず、4系だとspark.cssが見つからないとか言われる。本家のバグレポートでは、3系でバグレポートがでてるのに4.0でFixedということでバグがクローズされてる。このような開発状況を見るかぎりは flex-mojo を利用するのはリスクが高いですね、、、。今回はとりあえずasDocの生成をflex-sdkを直接使って行うだけに留めるけど、将来的には構成管理全般からflex-mojosを外すことにしよう。
"C:\Program Files\flex_sdk_4.1.0.16076\bin\asdoc.exe" -source-path . -doc-sources .
ちなみに、as3のソースコードの内容によって以下のようなエラーが出ることがある。
エラー: toplevel.xml を作成できませんでした : String index out of range: -41
原因は未確認。

2010年11月10日水曜日

非同期呼び出しについてのメモ

非同期呼び出しが絡んだ設計方法についてのメモです。

※以下、実装がマルチスレッドなのかシングルスレッドなのかは無視して考えましょう。

いい感じの本

Enterprise Integration Patterns とかいい感じっす。ECMA Script 系で必要とされるような非同期処理を扱う雰囲気の本ではなくて JMS や MQ 系のメッセージイングサーバを意識した本なのですが、最初の方の章が非同期処理に関する説明がいい感じで載っています。

おいらも読みなおしたいんだけど、日本に置いてきたので今は読めません(´・ω・`)

同期呼び出しのみの場合
スレッド1:処理A -> 処理B -> 処理C のように実行される。

ある処理が非同期処理に依存する場合

すべての処理が非同期だが、すべての処理が依存関係を持つ場合。
スレッド1:処理Aに処理Bをコールバック登録してフォーク 
スレッド2:     処理A実行 -> 処理Bに処理Cをコールバック登録してフォーク
スレッド3:            処理B実行 -> 処理Cにフォーク
スレッド4:                   処理C実行
この手の処理が、かなり面倒。そもそも概念的に非同期処理でないものを実装上強引に非同期処理にしているだけであり、同時並行処理のメリットが全く無い(シングルスレッド環境などの制約条件下でしかたなく対応するケースがほとんどだと思われ)。しかし、依存する処理をコールバックに変換するだけなので、同期呼び出しの場合からの機械的な変換は可能。この手の設計になる場合、同期処理で擬似コードを書いてから、その後機械的に非同期処理に変換したほうがいいかも。

非同期呼び出しにより処理がフォークする場合

各処理に依存関係がない場合は以下のように4つのスレッドが別々に動作する。
スレッド1:処理Aフォーク -> 処理Bフォーク -> 処理Cフォーク 
スレッド2:              -> 処理A
スレッド3:                               -> 処理B
スレッド4:                                               → 処理C
スレッド1の動作は一瞬にして行われるので、処理A,B,Cはほぼ同時に開始する。このような非同期処理は、人間から見てもそこそこ直感的。多くの場合シーケンシャルに動作させた場合よりも高い性能が得られる。

非同期呼び出し処理がジョインする場合

処理A,B,Cのすべてが完了したあとに次の処理が行われる場合の例
スレッド1:処理Aフォーク -> 処理Bフォーク -> 処理Cフォーク 
スレッド2:              -> 処理A -> 合流条件確認 -> (失敗)
スレッド3:                               -> 処理B                         -> 合流条件確認 -> (成功) -> 合流後の処理
スレッド4:                                                -> 処理C -> 合流条件確認 -> (失敗)
非同期処理のパフォーマンスを享受しつつ、処理の同期もおこなってしまうというおいしいところ取りですね。ジョインする条件の確認や、その条件を構成する情報の登録などをする必要があるのでコーディングレベルでは少しめんどくさいのですが、事前に設計をきちんとやっておけば問題ないでしょう。

設計方針考察

まずは、fork と join の発生する処理は避けて実装する。非同期処理を利用するとしても陸上のリレーのようにコールバックを用いて処理を継続させる方法であれば、join 条件のチェックが必要無いので設計がシンプルになる。この方法で、一通り実装して、実装した機能が仕様を満たしていることを確認する。

以下書きかけ。
  1. 設計対象の1つの処理を決定する。
  2. 処理内にて順次処理とフォークする処理とジョイン後の処理を、最外殻のレベルで分類し、各々の処理の塊をグループとする。
  3. 上記グループのうち、最外殻のジョイン処理とそれにジョインされる処理を1つのグループとして扱う。
  4. 複数のグループが残った場合、上記の各グループをメソッド化し、それそれを順次実行する。(各々が依存関係を持たないため並行動作が可能)
  5. 単一の順次処理の場合はそのまま記述すればよい。非同期処理は必要ない。
  6. 単一のジョインされるグループが残った場合、ジョイン実行条件のための情報(ジョイン実行条件判断クロージャから見えるスコープのフラグなど)を用意し、ジョイン実行条件を判断するクロージャを用意し、ジョイン実行条件のための情報の変更を通知するためのクロージャを用意し、ジョイン後の処理のクロージャを用意し、残りのグループを各メソッドとして作成して引数にジョイン実行条件判断用のクロージャと情報通知用クロージャとジョイン後の処理のクロージャを渡すようにする。
以降、上記の1からをネストして行う。
この説明じゃおいら自身以外にはわからんよねw まぁ、個人的なメモなので、、、。

まとめ

同一スレッドでの順次処理、フォーク、ジョインの3種類を適切に考えて設計すればよいということですね。

このような概念を用いた設計はプログラミング言語で記述することは直感的ではないと思うので(少なくともおいらには)、コーディングに入る前にアクティビティ図のような形で設計してみたほうがよいと思います。

2010年11月4日木曜日

Flash IDE 上でのアルファを変えるだけの簡単なボタンの作成

今回は、Flashの勘所を友人に教わりつつ、簡単なボタンのコンポーネントを作成してみました。

概要
任意のSpriteを継承したオブジェクトに対して、マウスのロールオーバー時に alpha の値を変更する振る舞いを付加するコンポーネントおよびライブラリです。

仕様
AlphaTransitiveButtonクラス
  • 概要
    • ロールオーバー時に alpha が変化する Sprite です。
    • ボタンモードはオンになります。
  • メリット
    • Flash IDE 上のシンボルからリンクするだけで、シンボルに対してロールオーバー時に alpha が 0.5 に変化する振る舞いを与えることができます。
    • 自前の ActionScript を作成する必要がありません。
  • 備考
    以下の場合、AlphaTransitiveButton クラスを使用せずに ButtonUtils の addXxxBehavior() 系のメソッドを利用の検討を推奨します。
    • 既に継承を使用しているために継承が使えない場合。
    • Alpha を独自に設定したい場合。
    • 継承を使いたくない場合。(例:10種類の振る舞いを追加するために10回継承するということを避けたい場合)
ButtonUtilsクラス
  • 概要
    • 指定された Sprite クラス(もしくは Sprite を継承したクラス)にロールオーバー時に alpha が変化する振る舞いを付加します。
    • ボタンモードはオンになります。
  • 備考
    • Flash IDE 上のシンボルに対して本メソッドのデフォルトの振る舞いを追加する場合は AlphaTransitiveButton クラスの利用も検討してみてください。
    サンプル

    • 上のボタンは AlphaTransitiveButton クラスにリンクしています。
    • 下のボタンは、ボタンのシンボルの1フレーム目で ButtonUtils#addAlphaTransitiveButtonBehavior(target, rollOverAlpha) を呼び出し、rollOverAlpha を0.8に設定しています。
    ソース

    AlphaTransitiveButtonクラス
    package {
        
        import flash.display.Sprite;
        import flash.events.MouseEvent;
        
        /**
         * ロールオーバー時に alpha が変化する Sprite です。
         * ボタンモードはオンになります。
         * 
         * メリット
         *  ・ Flash IDE 上のシンボルからリンクするだけで、シンボルに対してロールオーバー時に alpha が 0.5 に変化する振る舞いを与えることができます。
         *  ・ 自前の ActionScript を作成する必要がありません。
         * 
         * 備考
         *  ・ 以下の場合、AlphaTransitiveButton クラスを使用せずに ButtonUtils の addXxxBehavior() 系のメソッドを利用の検討を推奨します。
         *    ・ 既に継承を使用しているために継承が使えない場合。
         *    ・ Alpha を独自に設定したい場合。
         *    ・ 継承を使いたくない場合。(例:10種類の振る舞いを追加するために10回継承するということを避けたい場合)
         * 
         * @see ButtonUtils#addAlphaTransitiveButtonBehavior(Sprite, Number)
         */
        public class AlphaTransitiveButton extends Sprite {
            
            public function AlphaTransitiveButton() {
                ButtonUtils.addAlphaTransitiveButtonBehavior(this);
            }
        }
    }
    

    ButtonUtilsクラス
    package {
        
        import flash.display.Sprite;
        import flash.events.MouseEvent;
        
        /**
         * ボタン関連のユーティリティクラスです。
         */
        public class ButtonUtils extends Sprite {
            
            /**
             * 指定された Sprite クラス(もしくは Sprite を継承したクラス)にロールオーバー時に alpha が変化する振る舞いを付加します。
             * ボタンモードはオンになります。
             * Flash IDE 上のシンボルに対して本メソッドのデフォルトの振る舞いを追加する場合は AlphaTransitiveButton クラスの利用も検討してみてください。
             * 
             * 注: target が Dynamic Text の TextArea の場合など、特定の条件下でマウスオーバー時にマウスカーソルの形状が変化しない場合があります。(flash側の問題)
             * 
             * @param target 対象となる Sprite
             * @param rollOverAlpha ロールオーバー時の alpha の値
             * @see AlphaTransitiveButton
             */
            public static function addAlphaTransitiveButtonBehavior(target:Sprite, rollOverAlpha:Number = 0.5) {
                target.buttonMode = true;
                target.addEventListener(MouseEvent.ROLL_OVER, function(e:MouseEvent):void {
                    e.currentTarget.alpha = rollOverAlpha;
                });
                target.addEventListener(MouseEvent.ROLL_OUT, function(e:MouseEvent):void {
                    e.currentTarget.alpha = 1;
                });
            }
        }
    }
    
    アーカイブ

    2010年11月1日月曜日

    Flash IDE 上でのMVCについて考えてみた

    Flash IDE 上でのMVCについて考えてみた(その2) が最新です。

    近々、Flash IDE で開発を行うことになりました。

    開発に入る前の実験として、ステージ上に色々とコンポーネントを配置して、Documentクラスに ActionScript のコードを記述して、なんとなく動くものを作っていたというのがこれまでの話。しかし、これでは保守性に難があるなぁと思う今日この頃。

    保守性を向上させようと考えた場合に、すぐに思い浮かぶのはコンポーネント化。コンポーネント内部はシンプルなMVCがよさそう。

    ということで、実験してみました。

    方式
    • Flash IDE 上で作成したシンボルから as ファイルにリンクを張る方式。
    • 名前空間は Flash IDE 上の LIBRARY 内にフォルダを作成して対応する。

    今回作成するサンプル
    • TextArea に最初 0 が表示され、マウスでクリックするたびに値が 1 増えるアプリ。


    シンボル
    • LIBRARY 内のフォルダ名
      • com.objectfanatics.samplecomponent
    • シンボル名
      • View
    • リンク先のクラス
      • com.objectfanatics.samplecomponent.view.View

    クラス構成
    • com.objectfanatics.samplecomponent.model パッケージ
      • com.objectfanatics.samplecomponent.model.Model
        • コンポーネントのモデルのインタフェース。
        • モデルは可能な限り実装を意識しないで設計したいため、あえてインタフェースを作成。
      • com.objectfanatics.samplecomponent.model.ModelImpl
        • コンポーネントのモデルの実装。
      • com.objectfanatics.samplecomponent.model.ModelListener
        • Model の変更内容を監視するためのリスナーインタフェース
    • com.objectfanatics.samplecomponent.view パッケージ
      • com.objectfanatics.samplecomponent.view.View
        • Flash IDE 上で作成したシンボルからリンクされるクラス。
        • View として存在すると同時に、コンポーネント全体を構築する役割も持つ。
      • com.objectfanatics.samplecomponent.view.ModelListenerImpl
        • Model の変更を監視し、Model の変更内容に応じて View を更新するクラス。
        • ModelListenerインタフェースの実装クラス。
    • com.objectfanatics.samplecomponent.controller パッケージ
      • com.objectfanatics.samplecomponent.controller.Controller
        • View の内部要素に対してリスナーを登録し、イベントを監視する。
        • View からのイベントを処理し、必要に応じて Model を更新する。 

    クラスのソース
    package com.objectfanatics.samplecomponent.model {
     
     public interface Model {
      
      /**
       * ModelListener をセットします。
       * @param modelListener ModelListener
       */
      function setModelListener(modelListener:ModelListener):void;
      
      /**
       * 値を返します。
       * @return 値
       */
      function getNumber():uint;
      
      /**
       * 値を1増やします。
       */
      function increment():void;
     }
    }
    

    package com.objectfanatics.samplecomponent.model {
     
     public class ModelImpl implements Model {
      
      private var modelListener:ModelListener;
      
      /**
       * 保持される値
       */
      private var number:uint = 0;
      
      /**
       * Model#getNumber():uint の実装
       */
      public function getNumber():uint {
       return number;
      }
      
      /**
       * Model#setModelListener(modelListener:ModelListener):void の実装
       */
      public function setModelListener(modelListener:ModelListener):void {
       this.modelListener = modelListener;
      }
      
      /**
       * Model#increment():void の実装
       */
      public function increment():void {
       this.modelListener.numberChanged(++number);
      }
     }
    }
    

    package com.objectfanatics.samplecomponent.model {
     
     /**
      * Model の変更内容を監視するためのリスナー
      */
     public interface ModelListener {
      
      /**
       * 値が変更された場合に呼び出されます。
       * @param number 現在の値
       */
      function numberChanged(number:uint);
     }
    }
    

    package com.objectfanatics.samplecomponent.view {
     
     import flash.display.Sprite;
     
     import com.objectfanatics.samplecomponent.model.Model;
     import com.objectfanatics.samplecomponent.model.ModelImpl;
     import com.objectfanatics.samplecomponent.model.ModelListener;
     import com.objectfanatics.samplecomponent.controller.Controller;
     
     /**
      * FlashIDE 上の LIBRARY にて、フォルダ名が com.objectfanatics.samplecomponent、名前が View のシンボルに対応するクラス。
      * View という名前ですが、FlashIDE上で作成したシンボルからのリンクとなっているため、コンポーネント全体の初期化も行っています。
      * 純粋な View としての処理は、 Model には依存しますが Controller には依存しません。
      */
     public class View extends Sprite {
      
      private const model:Model = new ModelImpl();
      
      public function View():void {
       initView();
       model.setModelListener(new ModelListenerImpl(this));
       new Controller(this, model);
      }
      
      private function initView():void {
       this.numberText.text = "" + model.getNumber();
      }
     }
    }
    

    package com.objectfanatics.samplecomponent.view {
    
     import com.objectfanatics.samplecomponent.model.ModelListener;
      
     /**
      * Model の変更内容を監視し View に反映させるための ModelListener の実装クラス。
      */
     public class ModelListenerImpl implements ModelListener {
      
      /**
       * 変更対象の View
       */
      private var view:View;
      
      /**
       * コンストラクタ
       * @param view 変更対象の View
       */
      public function ModelListenerImpl(view:View):void {
       this.view = view;
      }
      
      /**
       * Model の値が変更された時に呼び出され、View 上の表示を変更します。
       * @param currentNumber 現在のの Model の値
       */
      public function numberChanged(currentNumber:uint) {
       view.numberText.text = "" + currentNumber;
      }
     }
    }
    

    package com.objectfanatics.samplecomponent.controller {
     
     import flash.events.MouseEvent;
     
     import com.objectfanatics.samplecomponent.view.View;
     import com.objectfanatics.samplecomponent.model.Model;
     
     public class Controller { 
      
      private var model:Model;
      
      private var view:View;
      
      public function Controller(view:View, model:Model):void {
       
       // Model と View の格納
       this.model = model;
       this.view = view;
       
       // View のクリックの検出設定
       view.addEventListener(MouseEvent.CLICK, function():void{model.increment()});
      }
     }
    }
    

    今回の所感

    単純な仕様のコンポーネントであれば、この方法でも十分な保守性が得られるのではないかと思いました。しかし、複雑になるとどうなるか全く見当も付きません。実際に複雑なケースに適用してみて実際に問題が生じたときに、その問題をシンプルに表現できる例題を作成して考察してみようと思います。

    全体的な仕組みとして再利用が効きそうなポイントが多々あるのですが、ことごとくうまくいきませんでした。たとえば、Viewクラスの内部は Template Method を適用すればコード量抑えられるしロジックを完全に隠蔽できるので保守性が上がりそうなものです。しかし、abstract class が使えないために Template Method のメリットがかなり小さくなってしまいます。通常のクラスで Template Method を試してもみたのですが、Generics が使えないために手動での強引な型変換が必要となり、ソースがかなり長く見辛くなってしまいました。

    まだ as3 を触り始めてまもないので、当面は粛々と実験していきたいと思います。

    アーカイブ