僕だってFlexでMVCっぽいことがしたかったんだ

いままでFlexのエントリをいくつか書いていますが、その場限りのお試しで終わっていて未だ初学者と言わずして何と言おうか、という立場の僕さ。
そんな人間がFlexMVCライクなことやりたいやりたい!と思っていろいろ調べてみました。世の中の大勢は以下のようです。

MVCフレームワークを使う

yui-frameworkPureMVCほかいろいろあるようですが、コレという決定版的なプロジェクトが見つけられませんでした。そしてそもそもFlexフレームワーク。初学の身としてはas3覚えてFlex覚えてさらにそのラッパフレームワークを覚えるのはコストが高い。その上先達が少ないとなれば学習コスト増の要因たりえるので及び腰になります。

IMXMLObjectを使う

これはけっこうな数のエントリが散見されました。なるほどビューとロジックが分離されてMVCっぽくできるようです。ただこの場合だとビューとコントローラの密な主従関係ができるよう。個人的な好みとしては都度任意のビューとコントローラを接続したい。

自分ならこうする

というわけで自分好みの枠組みを作ってみました。以下に「My」という意味のわからん名前のアプリを作る場合の例を示します。
その実、それほど難しいことはしていません。実際の開発ではapps配下とHogeA.asやHoge.mxmlに当たるモノを書いていけば良いと思います。当例はモデルは書いていませんがあしからず。また厳密に言えばBaseApplicationはエントリの本意から外れるのですがその点もご容赦いただければ。


ディレクトリ構成はこんな感じ。

|-- apps
|   `-- My
|       |-- My-config.xml
|       |-- My.mxml
|       `-- MyApp.as
`-- src
    `-- myproject
        |-- components
        |   |-- BaseApplication.as
        |   |-- controllers
        |   |   |-- BaseController.as
        |   |   |-- HogeA.as
        |   |   `-- HogeB.as
        |   `-- views
        |       `-- Hoge.mxml
        `-- models


ちょっと冗長ですが以降に各ファイルの中身を記述します。GitHubにでも載せとけって?え?聞こえない。

apps配下

My/My-config.xml

<?xml version="1.0"?>
<flex-config>
  <compiler>
    <source-path>
      <path-element>../../src</path-element>
    </source-path>
  </compiler>
</flex-config>

My/My.mxml

<?xml version="1.0" encoding="utf-8"?>
<local:MyApp xmlns:fx="http://ns.adobe.com/mxml/2009"
                      xmlns:s="library://ns.adobe.com/flex/spark"
                      xmlns:myprojectview="myproject.components.views.*"
                      xmlns:local="*">
  <myprojectview:Hoge id="buttonA" y="30"/>
  <myprojectview:Hoge id="buttonB" y="60"/>
</local:MyApp>

My/MyApp.as

package
{
    import flash.events.MouseEvent;
    import mx.controls.Alert;
    import myproject.components.BaseApplication;
    import myproject.components.controllers.HogeA;
    import myproject.components.controllers.HogeB;
    import spark.components.Button;

    public class MyApp extends BaseApplication
    {   
        /** 
         * 初期処理
         */
        protected override function init():void
        {   
            var button:Button = new Button();
            button.label = "MyApp";
            button.addEventListener(MouseEvent.CLICK, handleClick);
            addElement(button);

            // ビューとコントローラをつなぐ
            var ctrlA:HogeA = new HogeA();
            ctrlA.view = view.buttonA;

            var ctrlB:HogeB = new HogeB();
            ctrlB.view = view.buttonB;
        }   

        private function handleClick(e:MouseEvent):void
        {   
            Alert.show("MyApp Clicked!");
        }   
    }   
}
src配下

myproject/components/BaseApplication.as

package myproject.components
{
    import mx.events.FlexEvent;
    import spark.components.Application;

    /** 
     * すべてのアプリケーションのベースとなります。
     */
    public class BaseApplication extends Application
    {   
        /** 
         * アプリケーションのビューを格納します。
         */
        protected var view:Object;

        /** 
         * コンストラクタ
         */
        public function BaseApplication()
        {   
              super();
              addEventListener(FlexEvent.CREATION_COMPLETE, creationHandler);
        }   

        /** 
         * ビューのレンダリング完了後の処理
         */
        protected function creationHandler(e:FlexEvent):void
        {   
            view = e.target;
            init();
        }   

        /** 
         * 初期処理
         * ほとんどの場合オーバーライドされるはずです。
         */
        protected function init():void
        {   
            return;
        }   
    }   
}

myproject/components/controllers/BaseController.as

package myproject.components.controllers
{
    /** 
     * すべてのコントローラのベースになります。
     */
    public class BaseController
    {   
        /** 
         * コントロールを請け負うビューを格納します。
         */
        protected var _view:Object;

        /** 
         * ビューのセッタ
         */
        public function set view(value:Object):void
        {
            this._view = value;
            init();
        }

        /**
         * ビューのゲッタ
         */
        public function get view():Object
        {   
            return this._view;
        }       

        /**
         * 初期処理を行います.
         */
        protected function init():void
        {
            return;
        }
    }   
}

myproject/components/controllers/HogeA.as

package myproject.components.controllers
{
    import mx.controls.Alert;
    import flash.events.MouseEvent;

    public class HogeA extends BaseController
    {   
        /** 
         * 初期処理を行います.
         */
        protected override function init():void
        {   
            // ビューがクリックされたときのイベントリスナ登録
            this.view.addEventListener(MouseEvent.CLICK, handleClick);
        }   

        private function handleClick(e:MouseEvent):void
        {   
            Alert.show("HogeAだよ!");
        }   
    }   
}

myproject/components/controllers/HogeB.as

package myproject.components.controllers
{
    import mx.controls.Alert;
    import flash.events.MouseEvent;

    public class HogeB extends BaseController
    {   
        /** 
         * 初期処理を行います.
         */
        protected override function init():void
        {   
            // ビューがクリックされたときのイベントリスナ登録
            this.view.addEventListener(MouseEvent.CLICK, handleClick);
        }   

        private function handleClick(e:MouseEvent):void
        {   
            Alert.show("HogeBだよ!");
        }   
    }   
}

myproject/components/views/Hoge.mxml

<?xml version="1.0" encoding="utf-8"?>
<s:Button xmlns:fx="http://ns.adobe.com/mxml/2009"
          xmlns:s="library://ns.adobe.com/flex/spark"
          label="Hoge"/>