Robotlegs2を用いたFlash開発

tosikさんが投稿した[記事](http://qiita.com/tosik/items/45a7e68d3c987ca8d2e6)を参考に、[Robotlges2](http://www.robotlegs.org/)を使用したサンプルアプリケーションを作成しました。
Robotlegs2のGitHubは[コチラ](https://github.com/robotlegs/robotlegs-framework/blob/master/readme.md)

# Robotlegs2とは
Flashで[DIコンテナ](http://itpro.nikkeibp.co.jp/article/COLUMN/20060628/241982/?rt=nocnt)を実装するためのMVCS(Model-View-Controller-Service)フレームワークライブラリです。

# Robotlegsの利点
DIコンテナのフレームワークはいくつかありますが、RobotlegsがDIコンテナを実装する上で必要最低限の機能をそろえており、他のライブラリに比べて一番学習コストが低いです。
DIコンテナをとりあえず試用したい、勉強したい場合にオススメです。

※新卒２人に簡単にRobotlegs2の概要と実装方法を教えた後、約２週間ほどでライブラリを使いこなしていました。

# 実装サンプル

「テスト表示」のテキストをクリックすると「変更しました」と表示する単純なアプリです。

![クリック前.png](https://qiita-image-store.s3.amazonaws.com/0/104377/b5eb3b41-2422-dde1-619a-71e85fd4ca2b.png)

![クリック後.png](https://qiita-image-store.s3.amazonaws.com/0/104377/eee7d6aa-202e-1ad7-be9b-c5a13993b0d5.png)


今回のサンプルは[ここからダウンロード](https://github.com/YuukiOgino/Robotlegs2Sample)できます。

※サンプルはMVCモデルで実装しています。
※[FlashDevelop](http://flashdevelop.jp/)を使用しています。


# サンプルコードの説明

## **Main**

```lang:Main.as
	/**
	 * フレームワーク　Robotlegs2　サンプルプロジェクト
	 * as3であればGraphics,Stage3D、Flexで使用可能
	 * このプロジェクトはgraphicsで記載
	 * 
	 * @author ogino
	 */
	public class Main extends Sprite 
	{
		
		//-----------------------------------------------------
		//コンポーネント
		//-----------------------------------------------------			
		
		/**
		 * コンストラクタ
		 */
		public function Main():void 
		{
			if (stage) init();
			else addEventListener(Event.ADDED_TO_STAGE, init);
		}
		
		//-----------------------------------------------------
		//イベントハンドラー
		//-----------------------------------------------------			
		
		/**
		 * 初期化
		 * 
		 * @param	e Event
		 */
		private function init(e:Event = null):void 
		{
			removeEventListener(Event.ADDED_TO_STAGE, init);
			
			// Robotlegs2を起動するための設定
			// MVCSBundleをセット
			// コンフィグクラスをセット
			// 最後にコンテキストビューをセットし、初期化処理を実行
			
			var context:Context = new Context();
			// おまじない
			context.install(MVCSBundle);
			// コンフィグクラスをセット
			context.configure(MainConfig);
			// コンテキストビューとしてこの画面をセット
			context.configure(new ContextView(this));
			// Robotlegs2初期化
			context.initialize();
		}
		
	}
```


* * *

Robotlegs2を利用するにあたり、以下は必ず決まった書き方（おまじない）になります。

```lang:
var context:Context = new Context();
context.install(MVCSBundle);
context.configure(MainConfig);
context.configure(new ContextView(this));
context.initialize();
```

* * *

MVCSを実現するだけなら、MVCSBundleをinstallすれば大丈夫です。

```lang:
context.install(MVCSBundle);
```

* * *

ContextViewにドキュメントクラスを登録します。

```lang:
context.configure(new ContextView(this));
```

ドキュメントクラスでなくても大丈夫ですが、変な動作するのも怖いのでinitialzeメソッドを呼び出すクラスを登録するのが無難です。

* * *

コンフィグクラスは連続で設定することも可能です。

```lang:
context.configure(MainConfig).
　　　　　configure(ServiceConfig).
　　　　　configure(SocketServiceConfig);
```

* * *

最後にinitializeを呼び出します。これで初期化は終了です。

```lang:
context.initialize();
```

* * *

## **Config**

```lang:MainConfig.as
	/**
	 * メイン画面のコンフィギュレーションを行うクラス
	 * 
	 * このクラスで以下の設定を実行しています。
	 * 	・UserModel はシングルトンである
	 * 	・UserView の対になるメディエータは UserViewMediator である
	 * 　・メディエーターでUserChangeEvent.USER_CHANGEが呼ばれた場合、UserCommondを実行する
	 * 
	 * @author ogino
	 */
	public class MainConfig implements IConfig 
	{
	
		//-----------------------------------------------------
		//コンポーネント
		//-----------------------------------------------------			
		
		/** 画面 */
		private var userView:UserView = new UserView();		
		
		//-----------------------------------------------------
		//プロパティ
		//-----------------------------------------------------			
		
		[Inject]
		/** インジェクター */
		public var injector:IInjector;
		
		[Inject]
		/** メディエーターマップ */
		public var mediatorMap:IMediatorMap;
		
		[Inject]
		/** イベントコマンドマップ */
		public var commandMap:IEventCommandMap;				
		
		[Inject]
		/** コンテキストビュー */
		public var contextView:ContextView;
		
		//-----------------------------------------------------
		//メソッド
		//-----------------------------------------------------			
		
		/**
		 * コンフィギュレーション実行
		 */
		public function configure():void 
		{
			// おまじない
			
			// UserModel はシングルトンである
			injector.map(UserModel, "modelName").asSingleton();
			injector.map(UserModel).asSingleton();
			
			// UserView の対になるメディエータは UserViewMediator である
			mediatorMap.map(UserView).toMediator(UserViewMediator);
			
			// メディエーターでUserChangeEvent.USER_CHANGEが呼ばれた場合、UserCommondを実行する
			commandMap.map(UserChangeEvent.USER_CHANGE).toCommand(UserCommand);
			
			// このような形で値を注入することも可能
			//var user:UserModel = new UserModel();
			//injector.map(UserModel, "modelName").toValue(user);			
			
			// おまじないを設定すると、下記の処理が出来る
			
			// 画面を切り替える　このプロジェクトは一つしか画面がないので、このメソッドで初期化処理を実行
			contextView.view.addChild(userView);
			
			// DIコンテナからモデルを取得
			var m:UserModel = injector.getInstance(UserModel, "modelName");
			m.name = "テスト表示";
		}
		
	}
```

* * *

Configは必ずrobotlegs.bender.framework.api.IConfigをimplementsする必要があります。

```lang:
public class MainConfig implements IConfig
```

* * *

Configクラスを実装する上で、以下のプロパティを設定する必要があります。

```lang:
[Inject]
public var injector:IInjector;
[Inject]
public var mediatorMap:IMediatorMap;		
[Inject]
public var commandMap:IEventCommandMap;	
```

この３つはRobotlegs側が自動的に注入されるため、おまじないはありません。
※Injectタグをつけるプロパティは必ず**public**にしてください。

* * *

ドキュメント画面をConfigクラスで何か操作する場合、ContextViewをRobotlegsから注入する必要があります。

```lang:	
[Inject]
public var contextView:ContextView;
```

### configureメソッド

configureメソッドでDIコンテナ化するクラスを登録します。

* * *

シングルトン登録は以下の書き方になります。

```lang:通常
injector.map(UserModel).asSingleton();
```

```lang:name指定			
injector.map(UserModel, "modelName").asSingleton();
```

また、以下の方法でもシングルトンになります。

```lang:toValue
var user:UserModel = new UserModel();
injector.map(UserModel, "modelName").toValue(user);	
```

* * *

MediatorをViewに紐付けるには、以下の書き方になります。

```lang:
mediatorMap.map(UserView).toMediator(UserViewMediator);
```

これでUserViewの**対になるMediator**として、UserViewMediatorがRobotlegsに登録されました。
※MediatorとViewは必ず**1対1**になる仕様です。

* * *

Commondを実行するイベントの登録は、以下の書き方になります。

```lang:
commandMap.map(UserChangeEvent.USER_CHANGE).toCommand(UserCommand);
```

これでMediatorでUserChangeEvent.USER_CHANGEが呼ばれた場合、UserCommandクラスのメソッドが呼ばれるようになりました。

* * *

## **Mediator**

```lang:UserViewMediator.as
	/**
	 * ユーザー画面のメディエータクラス
	 * UserModelとUserViewの仲介を行う
	 * 「ユーザの名前が変更された」というきっかけで「ビューにその名前を反映する」タスクを実行するだけのクラス
	 * 
	 * @author ogino
	 */
	public class UserViewMediator extends Mediator
	{
		
		//-----------------------------------------------------
		//コンポーネント
		//-----------------------------------------------------			
		
		[Inject]
		/** ユーザ画面 */
		public var view:UserView;
		
		//-----------------------------------------------------
		//プロパティ
		//-----------------------------------------------------			
		
		[Inject(name="modelName")]
		/** ユーザモデル */
		public var user:UserModel;
		
		//-----------------------------------------------------
		//オーバーライドしたメソッド
		//-----------------------------------------------------			
		
		/**
		 * @inheritDoc
		 */
		public override function initialize():void
		{
			// 名前が変更されたときに呼び出すメソッドをセット
			user.addEventListener(Event.CHANGE, changeView);
			view.addEventListener(UserChangeEvent.USER_CHANGE, view_userChange);
		}
		
		/**
		 * @inheritDoc
		 */
		public override function destroy():void
		{
			// 登録したメソッドを削除
			user.removeEventListener(Event.CHANGE, changeView);
		}

		//-----------------------------------------------------
		//イベントハンドラー
		//-----------------------------------------------------			
		
		/**
		 * 画面更新
		 * 
		 * @param e Event
		 */
		public function changeView(e:Event):void
		{
			view.nameChange(user.name);
		}
		
		/**
		 * 画面変更通知イベントハンドラー
		 * 
		 * @param	e
		 */
		private function view_userChange(e:UserChangeEvent):void 
		{
			// イベントをクローンする
			var newEvent:UserChangeEvent = UserChangeEvent(e.clone());
			// フレームワークに通知　このイベントに紐付けられたCommandクラスが呼ばれる
			eventDispatcher.dispatchEvent(newEvent);
		}		
	}
```

* * *

Mediatorは必ず**robotlegs.bender.bundles.mvcs.Medator**クラスを継承する必要があります。


```lang:
public class UserViewMediator extends Mediator
```

* * *

Mediatorに紐付けられたView(この場合だとUserView)はMediator内でInjectタグで注入されます。

```lang:
[Inject]
public var view:UserView;
```

* * *

Configクラスでシングルトン登録されたクラス（この場合はUserModelクラス）がある場合、Robotlegs側から注入されます

```lang:
[Inject]
public var user:UserModel;
```

nameを指定すると、Configクラスでnameを指定することでRobotlegsから注入されます。
nameが違う場合、nameが無指定の場合は注入されません。

```lang:
[Inject(name="modelName")]
public var user:UserModel;
```

* * *

**MediatorクラスのeventDispatcher**に、Configクラスで登録したイベントクラスをdispatchEventに渡すことで、Commandクラスを実行できます。

```lang:
eventDispatcher.dispatchEvent(new UserChangeEvent(UserChangeEvent.USER_CHANGE));
```

※子画面からUserChangeEvent.USER_CHANGEをdispatchEventしてもCommandクラスは実行されないので注意

* * *

###オーバーライドしたメソッドの説明

* * *

1.initializeメソッド
　Mediatorに紐付けられたViewがstageに**addChild**されたタイミングでRobotlegs側から呼び出されます。

* * *

2.destroyメソッド
　Mediatorに紐付けられたViewがstageに**removeChild**されたタイミングでRobotlegs側から呼び出されます。

* * *


## **Command**

```lang:UserCommand.as
	/**
	 * ユーザーに関するコマンド実行
	 * このクラスはコンフィグで設定したイベントがメディエーターで呼ばれた場合、executeメソッドがフレームワーク側から実行される
	 * 他のクラスから、コマンドを直接実行させるのは控えること
	 * 
	 * @author ogino
	 */
	public class UserCommand extends Command
	{
		
		//-----------------------------------------------------
		//プロパティ
		//-----------------------------------------------------			
		
		[Inject]
		/** イベント　コマンドを実行したいイベントクラスをpublicで必ず記載すること */
		public var event:UserChangeEvent;		
		
		[Inject(name="modelName")]
		/** ユーザモデル */
		public var user:UserModel;		
		
		[Inject]
		/** コンテキストビュー */
		public var contextView:ContextView;		
		
		//-----------------------------------------------------
		//オーバーライドしたメソッド
		//-----------------------------------------------------			
		
		/**
		 * @inheritDoc
		 */
		override public function execute():void 
		{
			var view:UserView = new UserView();
			
			view.y = 20;
			user.name = "変更しました";
			// 画面を追加表示　コマンドクラスで画面遷移を実施するのがよさそう
			contextView.view.addChild(view);
		}
		
	}
```

RobotlegsにおいてCommandクラスはControllerです。

### **注意**

Commandクラスは**使い捨て**です。
Commandに紐付けられたイベントクラスがMediatorから呼ばれるたびに、Robotlegs内でnewされています。

* * *

Commandは必ず**robotlegs.bender.bundles.mvcs.Command**クラスを継承する必要があります。


```lang:
public class UserCommand extends Command
```

* * *

Commandクラスでは、Configクラスで紐付けられたイベントクラス（この場合はUserChangeEventクラス）をInjectタグで指定すると、Robotlegs側から注入されます。


```lang:
[Inject]
public var event:UserChangeEvent;
```

* * *

MVCSの場合、CommandクラスからServiceを呼び出します。

```lang:

[Inject]
public var service:SocketService;
				
override public function execute():void 
{		
　　　service.send();
}

```

* * *

###オーバーライドしたメソッドの説明

* * *

1.executeメソッド

　Commandに紐付けられたイベントクラスがMediator.eventDispatcher.dispatchEventを実行することで、Robotlegs側から呼び出されます。
　必ず**override**してください！

* * *

## **Model、View、Event**

Model、View、Eventは通常通りの実装です。

```lang:UserModel.as
	/**
	 * ユーザー名のモデルクラス
	 * 
	 * @author ogino
	 */
	public class UserModel extends EventDispatcher
	{
		
		//-----------------------------------------------------
		//プロパティ
		//-----------------------------------------------------		
		
		//---------------
		//name
		//---------------	
		
		/**
		 * @private
		 */
		private var _name:String;
		
		/**
		 * ユーザー名
		 */
		public function get name():String { return _name; }
		
		/**
		 * @private
		 */
		public function set name(value:String):void 
		{
			_name = value;
			// 変更を通知
			dispatchEvent(new Event(Event.CHANGE));
		}
	}
```

```lang:UserView.as
	/**
	 * ユーザー情報表示画面
	 * 
	 * @author ogino
	 */
	public class UserView extends Sprite 
	{
		
		//-----------------------------------------------------
		//コンポーネント
		//-----------------------------------------------------			
		
		/** ユーザー名表示領域 */
		private var nameTextField:TextField;
		
		//-----------------------------------------------------
		//コンストラクタ
		//-----------------------------------------------------			
		
		/**
		 * コンストラクタ
		 */
		public function UserView() 
		{
			super();
			
			nameTextField = new TextField();
			nameTextField.addEventListener(MouseEvent.CLICK, nameTextFieldClickHandler);
			addChild(nameTextField);
		}
		
		//-----------------------------------------------------
		//メソッド
		//-----------------------------------------------------			
		
		/**
		 * 名前変更
		 * 
		 * @param	name
		 */
		public function nameChange(name:String):void 
		{
			nameTextField.text = name;
		}
		
		//-----------------------------------------------------
		//イベントハンドラー
		//-----------------------------------------------------			
		
		/**
		 * 名前テキストをクリックしたときのイベントハンドラー
		 * 
		 * @param	e
		 */
		private function nameTextFieldClickHandler(e:MouseEvent):void 
		{
			var e:MouseEvent = new MouseEvent(MouseEvent.CLICK);
			dispatchEvent(e);
			
			// メディエーターに更新イベントを通知
			dispatchEvent(new UserChangeEvent(UserChangeEvent.USER_CHANGE));
		}
```

```lang:UserChangeEvent.as
	/**
	 * ユーザーがデーターを変更した時のイベントハンドラー
	 * 
	 * @author ogino
	 */
	public class UserChangeEvent extends Event 
	{
		
		//-----------------------------------------------------
		//イベント定数
		//-----------------------------------------------------			
		
		/** ユーザー変更 */
		public static const USER_CHANGE:String = "userChange";
		
		//-----------------------------------------------------
		//コンストラクタ
		//-----------------------------------------------------			
		
		/**
		 * コンストラクタ
		 * 
		 * @param	type
		 * @param	bubbles
		 * @param	cancelable
		 */
		public function UserChangeEvent(type:String, bubbles:Boolean=false, cancelable:Boolean=false) 
		{ 
			super(type, bubbles, cancelable);
		} 
		
		//-----------------------------------------------------
		//オーバーライドしたメソッド
		//-----------------------------------------------------			
		
		/**
		 * @inheritDoc
		 */
		override public function clone():Event 
		{
			var e:UserChangeEvent = new UserChangeEvent(type, bubbles, cancelable);
			return e;
		}
		
	}
```

以上、Robotlegs2 の実装したサンプルコードの説明になります。