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 を触り始めてまもないので、当面は粛々と実験していきたいと思います。

アーカイブ

0 件のコメント:

コメントを投稿