U⁄A LABO

RSS

SEARCH

AUTHOR

  • KNOCKKNOCK (10件)小遣い2万円のフラッシュデベロッパー。
    帰りが遅いのにキレる嫁をだましだまし制作中・・・。

  • TAKAIW (13件)フラッシュデベロッパー。遊びでゲームを作るのも好きです。外見に似合わないイラストを描くのも好きです。とにかく作るのが好きです。

  • CHIKATHREESIX (8件)メタル界のフラッシュデベロッパー。またある時はメタルギタリスト。オーサリング中もヘッドバンギングは絶やさない。

  • YANBOU (6件)犬と猫で、家が毎日運動会のフラッシュデベロッパー。
    ビールから発泡酒へ変更で節約中。

  • AKUZE (2件)2003年からユナイティアに寄生しているフラッシュデベロッパー。
    シルクドソレイユとカナダが好き。

  • DECO-DEKAI (1件)ビートダウンパートでご飯何杯も食べられます。
    ハードコア・テクニカルディレクターを目指して日々精進。

CATEGORY

HOME  >  第3回 Flash座談会 ~ASデザインパターン考証 MVC編~

2009.07.02 CHIKATHREESIX

第3回 Flash座談会 ~ASデザインパターン考証 MVC編~

こんにちは、続けてchikathreesixです。

ずいぶん前に行われた座談会の内容です...
もう少し早くエントリ書きます、、はい。

今沢山のAPIとやりとりを行う比較的アプリケーションに近いFlash案件を担当しているのですが、そういう時ってデータをどこで保持しておくかなど非常に頭を悩ませます。
そこで今回はO'REILLYから出版されている「ActionScript3.0 Design Patterns」から、前述の案件にも利用したMVCパターンについて発表させてもらいました。

MVCはご存じの方も多いかと思いますが、Model,View,Controllerの略です。
クラスをそれぞれに分けて設計します。
3つの特徴を簡単にまとめます。

Model
アプリケーションのデータとロジックを持ち、状態を管理します。
状態が変化したらそれを通知します。

View
表示要素です。ユーザーはViewを通してアプリケーションを操作します。

Controller
ModelとViewの橋渡しを行います。表示要素は持ちません。


三つのクラスの関係と、インタラクションがあったときの処理順序は以下のようになっています。(本の図を日本語にしただけです。。)
zu1.gif


では実際にどう実装するのか。
簡単な例で解説していきます。

Model
値を格納するメソッド、値を取りだすメソッドと、updateというメソッドを用意します。 これらのインターフェースを実装する形が理想かと思われます。
updateではCHANGEのイベントをディスパッチします。ここが結構ポイントです。

Model.as
package 
{
	import flash.events.Event;
	import flash.events.EventDispatcher;
	
	public class Model extends EventDispatcher implements IModel 
	{
		protected var _key:int;
		
		public function setKey(key:int):void {
			_key = key;
			this.update();
		}
		
		public function getKey():int {
			return _key;
		}
		
		protected function update():void {
			dispatchEvent(new Event(Event.CHANGE));
		}
	}	
}


Controller
ViewからModelに値を書き込むメソッドを用意します。これもインターフェースを作っておくとよいようです。

Controller.as
package 
{
	import flash.events.KeyboardEvent;
	
	public class Controller implements IController 
	{
		private var model:Object;
		
		public function Controller(aModel:Object) {
			this.model = aModel;
		}
		
		public function keyPressHandler(event:KeyboardEvent):void {
			(model as IModel).setKey(event.keyCode);
		}
	}
}


View
まず、表示要素のクラスComponentViewとそれを複数内包するCompositeViewというクラスを作成し、このいずれかを継承させます。
今回の例ではComponentViewを継承したViewを使用しますが、Viewが複数ある場合、Viewを包括するViewはCompositeViewを継承させます。

ComponentView.as
package
{
	import flash.display.Sprite;
	import flash.errors.IllegalOperationError;
	import flash.events.Event;

	/**
	* Viewの基本クラス
	*/
	public class ComponentView extends Sprite
	{
		protected var model:Object;
		protected var controller:Object;
		
		public function ComponentView(aModel:Object = null, aController:Object = null) 
		{
			this.model = aModel;
			this.controller = aController;
		}

		public function setModelController(aModel:Object = null, aController:Object = null):void
		{
			this.model = aModel;
			this.controller = aController;
		}

		public function add(c:ComponentView):void
		{
			throw new IllegalOperationError("add operation not supported");
		}
		
		public function remove(c:ComponentView):void
		{
			throw new IllegalOperationError("remove operation not supported");
		}
		
		public function getChild(n:int):ComponentView
		{
			throw new IllegalOperationError("getChild operation not supported");
			return null;
		}
		
		//ABSTRACT Method
		public function update(e:Event = null):void{}
	}
}

CompositeView.as
package
{
	import com.chikathreesix.mvc.ComponentView;
	import flash.events.Event;
	
	/**
	* 複数のViewを持つView
	*/
	public class CompositeView extends ComponentView
	{
		private var aChildren:Array;
		
		public function CompositeView(aModel:Object = null, aController:Object = null) 
		{
			super(aModel, aController);
			this.aChildren = new Array();
		}
		
		override public function setModelController(aModel:Object = null, aController:Object = null):void 
		{
			super.setModelController(aModel, aController);
			for each(var c:ComponentView in aChildren)
			{
				c.setModelController(aModel, aController);
			}
		}
		
		override public function add(c:ComponentView):void 
		{
			aChildren.push(c);
		}
	
		override public function remove(c:ComponentView):void 
		{
			super.remove(c);
			for (var i = 0; i < aChildren.length; i++)
			{
				if (aChildren[i] == c)
				{
					aChildren.splice(i, 1);
				}
			}
		}
		
		override public function update(e:Event = null):void 
		{
			for each(var c:ComponentView in aChildren)
			{
				c.update(e);
			}
		}
	}
}

setModelControllerというのは自前です。静的配置するものがほとんどだったので、モデルとコントローラーを入れるために作りました。

View.as
package 
{
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	
	public class View extends ComponentView
	{
		public function ComponentView(aModel:Object = null, aController:Object = null) 
		{
			super(aModel, aController);
			this.addEventListener(KeyboardEvent.KEY_DOWN, _onKeyPress);
		}
		
		private function _onKeyPress(event:KeyboardEvent):void {
			(controller as IController).setKey(event);
		}
		
		
		override public function update(e:Event = null):void 
		{
			super.update(e);
			trace((model as IModel).getKey());
		}
	}
}


ドキュメントクラス
Model、View、Controllerを生成し、紐付けます。ModelのCHANGEイベントをViewが監視することで、Modelの値が変化した時にそれを即座に反映することができます。

DocumentClass.as
package 
{
	import flash.display.Sprite;
	import flash.events.Event;
	
	public class DocumentClass extends Sprite 
	{
		public function DocumentClass() {
			var model:IModel = new Model();
			var controller:IController = new Controller(model);
			
			var view:View = new View(model, controller);
			model.addEventListener(Event.CHANGE, view.update);
		}
	}
}


クラスの量が膨大になる場合、Model、View、Controllerをどう管理するか、本には載っていませんでした。
いろいろ設計手法はあると思いますが、僕はCoreModelというものをつくり、ModelがCoreModelのイベントを監視することで、すべてのデータを一元管理し、変更した場合、すべてのModelに通達するように設計しました。
これが最適解ではないかもしれません。もしいい方法があったら教えていただきたいです。
CATEGORY